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
IndentCaseLabels: true
IndentWidth: 4
IndentWrappedFunctionNames: true
IndentPPDirectives: AfterHash
NamespaceIndentation: Inner
PointerBindsToType: false
SpacesBeforeTrailingComments: 2
Standard: Auto

View file

@ -10,11 +10,11 @@
### OpenSSL
#### 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
3. When prompted, copy the OpenSSL DLLs to "The OpenSSL binaries (/bin) directory"
#### 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
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
Jenkinsfile vendored
View file

@ -6,12 +6,12 @@ pipeline {
parallel {
stage('GCC') {
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') {
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(----)
QT += widgets core gui network multimedia svg
QT += widgets core gui network multimedia svg concurrent
CONFIG += communi
COMMUNI += core model util
CONFIG += c++14
@ -33,7 +33,7 @@ equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
}
# Icons
macx:ICON = resources/images/chatterino2.icns
#macx:ICON = resources/images/chatterino2.icns
win32:RC_FILE = resources/windows.rc
@ -72,6 +72,8 @@ win32 {
# OSX include directory
macx {
INCLUDEPATH += /usr/local/include
INCLUDEPATH += /usr/local/opt/openssl/include
LIBS += -L/usr/local/opt/openssl/lib
}
# Optional dependency on Windows SDK 7
@ -106,7 +108,6 @@ SOURCES += \
src/Application.cpp \
src/common/Channel.cpp \
src/common/CompletionModel.cpp \
src/common/Emotemap.cpp \
src/common/NetworkData.cpp \
src/common/NetworkManager.cpp \
src/common/NetworkRequest.cpp \
@ -188,8 +189,6 @@ SOURCES += \
src/widgets/helper/NotebookButton.cpp \
src/widgets/helper/NotebookTab.cpp \
src/widgets/helper/ResizingTextEdit.cpp \
src/widgets/helper/RippleEffectButton.cpp \
src/widgets/helper/RippleEffectLabel.cpp \
src/widgets/helper/ScrollbarHighlight.cpp \
src/widgets/helper/SearchPopup.cpp \
src/widgets/helper/SettingsDialogTab.cpp \
@ -239,7 +238,6 @@ SOURCES += \
src/providers/twitch/PubsubClient.cpp \
src/providers/twitch/TwitchApi.cpp \
src/messages/Emote.cpp \
src/messages/EmoteMap.cpp \
src/messages/ImageSet.cpp \
src/providers/bttv/BttvEmotes.cpp \
src/providers/ffz/FfzEmotes.cpp \
@ -257,16 +255,20 @@ SOURCES += \
src/controllers/notifications/NotificationModel.cpp \
src/singletons/Toasts.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 += \
src/Application.hpp \
src/common/Channel.hpp \
src/common/Common.hpp \
src/common/CompletionModel.hpp \
src/common/Emotemap.hpp \
src/common/FlagsEnum.hpp \
src/common/LockedObject.hpp \
src/common/MutexValue.hpp \
src/common/Atomic.hpp \
src/common/NetworkCommon.hpp \
src/common/NetworkData.hpp \
src/common/NetworkManager.hpp \
@ -276,9 +278,8 @@ HEADERS += \
src/common/NetworkTimer.hpp \
src/common/NetworkWorker.hpp \
src/common/NullablePtr.hpp \
src/common/Property.hpp \
src/common/ProviderId.hpp \
src/common/SerializeCustom.hpp \
src/util/RapidJsonSerializeQString.hpp \
src/common/SignalVectorModel.hpp \
src/common/Version.hpp \
src/controllers/accounts/Account.hpp \
@ -314,7 +315,6 @@ HEADERS += \
src/messages/MessageBuilder.hpp \
src/messages/MessageColor.hpp \
src/messages/MessageElement.hpp \
src/messages/MessageParseArgs.hpp \
src/messages/Selection.hpp \
src/PrecompiledHeader.hpp \
src/providers/emoji/Emojis.hpp \
@ -381,8 +381,6 @@ HEADERS += \
src/widgets/helper/NotebookButton.hpp \
src/widgets/helper/NotebookTab.hpp \
src/widgets/helper/ResizingTextEdit.hpp \
src/widgets/helper/RippleEffectButton.hpp \
src/widgets/helper/RippleEffectLabel.hpp \
src/widgets/helper/ScrollbarHighlight.hpp \
src/widgets/helper/SearchPopup.hpp \
src/widgets/helper/SettingsDialogTab.hpp \
@ -426,7 +424,6 @@ HEADERS += \
src/singletons/Updates.hpp \
src/singletons/NativeMessaging.hpp \
src/singletons/Theme.hpp \
src/common/SimpleSignalVector.hpp \
src/common/SignalVector.hpp \
src/widgets/dialogs/LogsPopup.hpp \
src/common/Singleton.hpp \
@ -439,8 +436,6 @@ HEADERS += \
src/providers/twitch/PubsubClient.hpp \
src/providers/twitch/TwitchApi.hpp \
src/messages/Emote.hpp \
src/messages/EmoteMap.hpp \
src/messages/EmoteCache.hpp \
src/messages/ImageSet.hpp \
src/common/Outcome.hpp \
src/providers/bttv/BttvEmotes.hpp \
@ -460,6 +455,12 @@ HEADERS += \
src/controllers/notifications/NotificationModel.hpp \
src/singletons/Toasts.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.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/notifications/NotificationController.hpp"
#include "controllers/taggedusers/TaggedUsersController.hpp"
#include "debug/Log.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/bttv/BttvEmotes.hpp"
#include "providers/ffz/FfzEmotes.hpp"
#include "providers/twitch/PubsubClient.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Fonts.hpp"
#include "singletons/Logging.hpp"
#include "singletons/NativeMessaging.hpp"
@ -23,6 +25,7 @@
#include "singletons/WindowManager.hpp"
#include "util/IsBigEndian.hpp"
#include "util/PostToThread.hpp"
#include "widgets/Window.hpp"
#include <atomic>
@ -37,9 +40,7 @@ Application *Application::instance = nullptr;
// to each other
Application::Application(Settings &_settings, Paths &_paths)
: settings(&_settings)
, paths(&_paths)
, resources(&this->emplace<Resources2>())
: resources(&this->emplace<Resources2>())
, themes(&this->emplace<Theme>())
, fonts(&this->emplace<Fonts>())
@ -103,26 +104,26 @@ void Application::save()
void Application::initNm()
{
#ifdef Q_OS_WIN
#ifdef QT_DEBUG
#ifdef C_DEBUG_NM
# ifdef QT_DEBUG
# ifdef C_DEBUG_NM
this->nativeMessaging->registerHost();
this->nativeMessaging->openGuiMessageQueue();
#endif
#else
# endif
# else
this->nativeMessaging->registerHost();
this->nativeMessaging->openGuiMessageQueue();
#endif
# endif
#endif
}
void Application::initPubsub()
{
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) {
Log("WHISPER RECEIVED LOL"); //
log("WHISPER RECEIVED LOL"); //
});
this->twitch.pubsub->signals_.moderation.chatCleared.connect(

View file

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

View file

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

View file

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

View file

@ -12,7 +12,7 @@
#include "widgets/dialogs/LastRunCrashDialog.hpp"
#ifdef C_USE_BREAKPAD
#include <QBreakpadHandler.h>
# include <QBreakpadHandler.h>
#endif
// void initQt();
@ -23,82 +23,82 @@
namespace chatterino {
namespace {
void installCustomPalette()
{
// borrowed from
// https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
QPalette darkPalette = qApp->palette();
void installCustomPalette()
{
// borrowed from
// https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
auto dark = qApp->palette();
darkPalette.setColor(QPalette::Window, QColor(22, 22, 22));
darkPalette.setColor(QPalette::WindowText, Qt::white);
darkPalette.setColor(QPalette::Text, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText,
QColor(127, 127, 127));
darkPalette.setColor(QPalette::Base, QColor("#333"));
darkPalette.setColor(QPalette::AlternateBase, QColor("#444"));
darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::Text,
QColor(127, 127, 127));
darkPalette.setColor(QPalette::Dark, QColor(35, 35, 35));
darkPalette.setColor(QPalette::Shadow, QColor(20, 20, 20));
darkPalette.setColor(QPalette::Button, QColor(70, 70, 70));
darkPalette.setColor(QPalette::ButtonText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText,
QColor(127, 127, 127));
darkPalette.setColor(QPalette::BrightText, Qt::red);
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Disabled, QPalette::Highlight,
QColor(80, 80, 80));
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText,
QColor(127, 127, 127));
dark.setColor(QPalette::Window, QColor(22, 22, 22));
dark.setColor(QPalette::WindowText, Qt::white);
dark.setColor(QPalette::Text, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::WindowText,
QColor(127, 127, 127));
dark.setColor(QPalette::Base, QColor("#333"));
dark.setColor(QPalette::AlternateBase, QColor("#444"));
dark.setColor(QPalette::ToolTipBase, Qt::white);
dark.setColor(QPalette::ToolTipText, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::Text,
QColor(127, 127, 127));
dark.setColor(QPalette::Dark, QColor(35, 35, 35));
dark.setColor(QPalette::Shadow, QColor(20, 20, 20));
dark.setColor(QPalette::Button, QColor(70, 70, 70));
dark.setColor(QPalette::ButtonText, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::ButtonText,
QColor(127, 127, 127));
dark.setColor(QPalette::BrightText, Qt::red);
dark.setColor(QPalette::Link, QColor(42, 130, 218));
dark.setColor(QPalette::Highlight, QColor(42, 130, 218));
dark.setColor(QPalette::Disabled, QPalette::Highlight,
QColor(80, 80, 80));
dark.setColor(QPalette::HighlightedText, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::HighlightedText,
QColor(127, 127, 127));
qApp->setPalette(darkPalette);
}
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);
}
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
}
void createRunningFile(const QString &path)
{
QFile runningFile(path);
QApplication::setStyle(QStyleFactory::create("Fusion"));
runningFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
runningFile.flush();
runningFile.close();
}
installCustomPalette();
}
void removeRunningFile(const QString &path)
{
QFile::remove(path);
}
void showLastCrashDialog()
{
#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
void runGui(QApplication &a, Paths &paths, Settings &settings)
@ -109,7 +109,7 @@ void runGui(QApplication &a, Paths &paths, Settings &settings)
chatterino::Updates::getInstance().checkForUpdates();
#ifdef C_USE_BREAKPAD
QBreakpadInstance.setDumpPath(app->paths->settingsFolderPath + "/Crashes");
QBreakpadInstance.setDumpPath(getPaths()->settingsFolderPath + "/Crashes");
#endif
// Running file

View file

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

View file

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

View file

@ -18,16 +18,14 @@
namespace chatterino {
//
// Channel
//
Channel::Channel(const QString &name, Type type)
: completionModel(name)
: completionModel(*this)
, name_(name)
, type_(type)
{
QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout,
[this]() {
this->completionModel.clearExpiredStrings(); //
});
this->clearCompletionModelTimer_.start(60 * 1000);
}
Channel::~Channel()
@ -67,8 +65,7 @@ void Channel::addMessage(MessagePtr message)
const QString &username = message->loginName;
if (!username.isEmpty()) {
// TODO: Add recent chatters display name. This should maybe be a
// setting
// TODO: Add recent chatters display name
this->addRecentChatter(message);
}
@ -98,8 +95,6 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
for (int i = snapshotLength - 1; i >= end; --i) {
auto &s = snapshot[i];
qDebug() << s->parseTime << minimumTime;
if (s->parseTime < minimumTime) {
break;
}
@ -165,13 +160,14 @@ void Channel::disableAllMessages()
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
int snapshotLength = snapshot.getLength();
for (int i = 0; i < snapshotLength; i++) {
auto &s = snapshot[i];
if (s->flags.hasAny({MessageFlag::System, MessageFlag::Timeout})) {
auto &message = snapshot[i];
if (message->flags.hasAny(
{MessageFlag::System, MessageFlag::Timeout})) {
continue;
}
// 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)
{
// Do nothing by default
}
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

View file

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

View file

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

View file

@ -2,45 +2,30 @@
#include "Application.hpp"
#include "common/Common.hpp"
#include "common/UsernameSet.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandController.hpp"
#include "debug/Benchmark.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Emotes.hpp"
#include <QtAlgorithms>
#include <utility>
namespace chatterino {
// -- TaggedString
//
// TaggedString
//
CompletionModel::TaggedString::TaggedString(const QString &_str, Type _type)
: str(_str)
CompletionModel::TaggedString::TaggedString(const QString &_string, Type _type)
: string(_string)
, 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
{
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
{
if (this->isEmote()) {
if (that.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 (this->isEmote() != that.isEmote()) {
return this->isEmote();
}
if (that.isEmote()) {
return false;
}
int k = QString::compare(this->str, that.str, Qt::CaseInsensitive);
if (k == 0) {
return false;
}
// try comparing insensitively, if they are the same then senstively
// (fixes order of LuL and LUL)
int k = QString::compare(this->string, that.string, Qt::CaseInsensitive);
if (k == 0) return this->string > that.string;
return k < 0;
}
// -- CompletionModel
CompletionModel::CompletionModel(const QString &channelName)
: channelName_(channelName)
//
// CompletionModel
//
CompletionModel::CompletionModel(Channel &channel)
: channel_(channel)
{
}
@ -87,160 +60,90 @@ int CompletionModel::columnCount(const QModelIndex &) 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->emotes_.begin();
auto it = this->items_.begin();
std::advance(it, index.row());
return QVariant(it->str);
return QVariant(it->string);
}
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
if (auto account = app->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
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();
}
}
auto addString = [&](const QString &str, TaggedString::Type type) {
if (str.startsWith(prefix, Qt::CaseInsensitive))
this->items_.emplace(str + " ", type);
};
add(username);
add("@" + username);
}
if (auto channel = dynamic_cast<TwitchChannel *>(&this->channel_)) {
// 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()
{
std::lock_guard<std::mutex> lock(this->emotesMutex_);
// Usernames
if (prefix.length() >= UsernameSet::PrefixLength) {
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();) {
const auto &taggedString = *it;
// Bttv Global
for (auto &emote : *channel->globalBttv().emotes()) {
addString(emote.first.string, TaggedString::Type::BTTVChannelEmote);
}
if (taggedString.isExpired(now)) {
// Log("String {} expired", taggedString.str);
it = this->emotes_.erase(it);
} else {
++it;
// Ffz Global
for (auto &emote : *channel->globalFfz().emotes()) {
addString(emote.first.string, TaggedString::Type::FFZChannelEmote);
}
// 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

View file

@ -1,7 +1,5 @@
#pragma once
#include "common/Common.hpp"
#include <QAbstractListModel>
#include <chrono>
@ -10,7 +8,7 @@
namespace chatterino {
class TwitchChannel;
class Channel;
class CompletionModel : public QAbstractListModel
{
@ -33,39 +31,30 @@ class CompletionModel : public QAbstractListModel
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 operator<(const TaggedString &that) const;
QString str;
// Type will help decide the lifetime of the tagged strings
QString string;
Type type;
mutable std::chrono::steady_clock::time_point timeAdded;
};
public:
CompletionModel(const QString &channelName);
CompletionModel(Channel &channel);
virtual int columnCount(const QModelIndex &) const override;
virtual QVariant data(const QModelIndex &index, int) const override;
virtual int rowCount(const QModelIndex &) const override;
void refresh();
void addString(const QString &str, TaggedString::Type type);
void addUser(const QString &str);
void clearExpiredStrings();
void refresh(const QString &prefix);
private:
TaggedString createUser(const QString &str);
mutable std::mutex emotesMutex_;
std::set<TaggedString> emotes_;
QString channelName_;
std::set<TaggedString> items_;
mutable std::mutex itemsMutex_;
Channel &channel_;
};
} // 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 {
// = std::enable_if<std::is_enum<T>::value>::type
template <typename T, typename Q = typename std::underlying_type<T>::type>
class FlagsEnum
{
public:
FlagsEnum()
: value(static_cast<T>(0))
: value_(static_cast<T>(0))
{
}
FlagsEnum(T value)
: value(value)
: value_(value)
{
}
@ -29,22 +27,22 @@ public:
bool operator==(const FlagsEnum<T> &other)
{
return this->value == other.value;
return this->value_ == other.value_;
}
bool operator!=(const FlagsEnum &other)
{
return this->value != other.value;
return this->value_ != other.value_;
}
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)
{
reinterpret_cast<Q &>(this->value) &= ~static_cast<Q>(flag);
reinterpret_cast<Q &>(this->value_) &= ~static_cast<Q>(flag);
}
void set(T flag, bool value)
@ -57,33 +55,17 @@ public:
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
{
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
{
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);
}
@ -93,7 +75,7 @@ public:
}
private:
T value;
T value_{};
};
} // namespace chatterino

View file

@ -5,69 +5,63 @@
#include <QString>
#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 {
LinkParser::LinkParser(const QString &unparsedString)
{
static QRegularExpression linkRegex = [] {
static QRegularExpression newLineRegex("\r?\n");
QFile tldFile(":/tlds.txt");
tldFile.open(QFile::ReadOnly);
QFile file(":/tlds.txt");
file.open(QFile::ReadOnly);
QTextStream tlds(&file);
tlds.setCodec("UTF-8");
QTextStream t1(&tldFile);
t1.setCodec("UTF-8");
// tldData gets injected into the LINK macro
auto tldData = tlds.readAll().replace(newLineRegex, "|");
(void)tldData;
// Read the TLDs in and replace the newlines with pipes
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,
return QRegularExpression(LINK,
QRegularExpression::CaseInsensitiveOption);
}();
this->match_ = linkRegex.match(unparsedString);
}
bool LinkParser::hasMatch() const
{
return this->match_.hasMatch();
}
QString LinkParser::getCaptured() const
{
return this->match_.captured();
}
} // namespace chatterino

View file

@ -10,15 +10,8 @@ class LinkParser
public:
explicit LinkParser(const QString &unparsedString);
bool hasMatch() const
{
return this->match_.hasMatch();
}
QString getCaptured() const
{
return this->match_.captured();
}
bool hasMatch() const;
QString getCaptured() const;
private:
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 "Common.hpp"
class QNetworkReply;
namespace chatterino {
class Outcome;
class NetworkResult;
using NetworkSuccessCallback = std::function<Outcome(NetworkResult)>;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,13 @@
#pragma once
#include "Application.hpp"
#include "common/NetworkCommon.hpp"
#include "common/NetworkData.hpp"
#include "common/NetworkRequester.hpp"
#include "common/NetworkResult.hpp"
#include "common/NetworkTimer.hpp"
#include "common/NetworkWorker.hpp"
namespace chatterino {
class NetworkData;
class NetworkRequest
{
@ -27,19 +26,15 @@ class NetworkRequest
bool executed_ = false;
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(
const std::string &url,
NetworkRequestType requestType = NetworkRequestType::Get);
explicit NetworkRequest(
QUrl url, NetworkRequestType requestType = NetworkRequestType::Get);
NetworkRequest(NetworkRequest &&other) = default;
NetworkRequest &operator=(NetworkRequest &&other) = default;
~NetworkRequest();
void setRequestType(NetworkRequestType newRequestType);
@ -55,14 +50,13 @@ public:
void setRawHeader(const char *headerName, const QByteArray &value);
void setRawHeader(const char *headerName, const QString &value);
void setTimeout(int ms);
void setExecuteConcurrently(bool value);
void makeAuthorizedV5(const QString &clientID,
const QString &oauthToken = QString());
void execute();
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
Outcome tryLoadCachedFile();

View file

@ -31,7 +31,7 @@ rapidjson::Document NetworkResult::parseRapidJson() const
ret.Parse(this->data_.data(), this->data_.length());
if (result.Code() != rapidjson::kParseErrorNone) {
Log("JSON parse error: {} ({})",
log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset());
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:
T *element_;
std::mutex *mutex_;
bool isValid_ = true;
T *element_{};
std::mutex *mutex_{};
bool isValid_{true};
};
template <typename T>
class UniqueAccess
{
public:
// template <typename X = decltype(T())>
UniqueAccess()
: element_(T())
{
@ -88,7 +87,8 @@ public:
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
{
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"
#if defined(Q_OS_WIN)
#define CHATTERINO_OS "win"
# define CHATTERINO_OS "win"
#elif defined(Q_OS_MACOS)
#define CHATTERINO_OS "macos"
# define CHATTERINO_OS "macos"
#elif defined(Q_OS_LINUX)
#define CHATTERINO_OS "linux"
# define CHATTERINO_OS "linux"
#endif

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -17,12 +17,16 @@ void ModerationActions::initialize(Settings &settings, Paths &paths)
assert(!this->initialized_);
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.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);
private:
ChatterinoSetting<std::vector<ModerationAction>> setting_ = {
"/moderation/actions"};
std::unique_ptr<ChatterinoSetting<std::vector<ModerationAction>>> setting_;
bool initialized_ = false;
};

View file

@ -29,12 +29,12 @@ void TaggedUsersModel::afterInit()
{
// std::vector<QStandardItem *> row = this->createRow();
// setBoolItem(row[0],
// getApp()->settings->enableHighlightsSelf.getValue(), true, false);
// getSettings()->enableHighlightsSelf.getValue(), true, false);
// row[0]->setData("Your username (automatic)", Qt::DisplayRole);
// setBoolItem(row[1],
// getApp()->settings->enableHighlightTaskbar.getValue(), true, false);
// getSettings()->enableHighlightTaskbar.getValue(), true, false);
// setBoolItem(row[2],
// getApp()->settings->enableHighlightSound.getValue(), true, false);
// getSettings()->enableHighlightSound.getValue(), true, false);
// row[3]->setFlags(0); this->insertCustomRow(row, 0);
}
@ -45,17 +45,17 @@ void TaggedUsersModel::afterInit()
// switch (column) {
// case 0: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightsSelf.setValue(value.toBool());
// getSettings()->enableHighlightsSelf.setValue(value.toBool());
// }
// } break;
// case 1: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightTaskbar.setValue(value.toBool());
// getSettings()->enableHighlightTaskbar.setValue(value.toBool());
// }
// } break;
// case 2: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightSound.setValue(value.toBool());
// getSettings()->enableHighlightSound.setValue(value.toBool());
// }
// } break;
// 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
#include <QDebug>
#include "debug/Log.hpp"
#include <QElapsedTimer>
#include <boost/current_function.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 {
class BenchmarkGuard : boost::noncopyable
{
QElapsedTimer timer;
QString name;
public:
BenchmarkGuard(const QString &_name)
: name(_name)
{
timer.start();
}
BenchmarkGuard(const QString &_name);
~BenchmarkGuard();
qreal getElapsedMs();
~BenchmarkGuard()
{
qDebug() << this->name << float(timer.nsecsElapsed()) / 1000000.0f
<< "ms";
}
qreal getElapsedMs()
{
return qreal(timer.nsecsElapsed()) / 1000000.0;
}
private:
QElapsedTimer timer_;
QString name_;
};
} // namespace chatterino

View file

@ -8,17 +8,22 @@
namespace chatterino {
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")
<< fS(formatString, std::forward<Args>(args)...).c_str();
}
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")
<< fS(formatString, std::forward<Args>(args)...).c_str();
log(std::string(formatString), std::forward<Args>(args)...);
}
template <typename... Args>
inline void log(const QString &formatString, Args &&... args)
{
log(formatString.toStdString(), std::forward<Args>(args)...);
}
} // namespace chatterino

View file

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

View file

@ -15,61 +15,31 @@ bool operator!=(const Emote &a, const Emote &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)
// : data_(data)
//{
//}
//
// Emote::Emote(const EmoteData2 &data)
// : data_(data)
//{
//}
//
// const Url &Emote::getHomePage() const
//{
// return this->data_.homePage;
//}
//
// 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);
//}
EmotePtr cachedOrMakeEmotePtr(
Emote &&emote,
std::unordered_map<EmoteId, std::weak_ptr<const Emote>> &cache,
std::mutex &mutex, const EmoteId &id)
{
std::lock_guard<std::mutex> guard(mutex);
auto shared = cache[id].lock();
if (shared && *shared == emote) {
// reuse old shared_ptr if nothing changed
return shared;
} else {
shared = std::make_shared<Emote>(std::move(emote));
cache[id] = shared;
return shared;
}
}
} // namespace chatterino

View file

@ -7,9 +7,6 @@
#include <memory>
#include <unordered_map>
QStringAlias(EmoteId);
QStringAlias(EmoteName);
namespace chatterino {
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 WeakEmoteIdMap = std::unordered_map<EmoteId, std::weak_ptr<const Emote>>;
// struct EmoteData2 {
// EmoteName name;
// ImageSet images;
// Tooltip tooltip;
// Url homePage;
//};
//
// 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_;
//};
EmotePtr cachedOrMakeEmotePtr(Emote &&emote, const EmoteMap &cache);
EmotePtr cachedOrMakeEmotePtr(
Emote &&emote,
std::unordered_map<EmoteId, std::weak_ptr<const Emote>> &cache,
std::mutex &mutex, const EmoteId &id);
} // 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 "Application.hpp"
#include "common/Common.hpp"
#include "common/NetworkRequest.hpp"
#include "debug/AssertInGuiThread.hpp"
#include "debug/Benchmark.hpp"
#include "debug/Log.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/WindowManager.hpp"
@ -15,119 +17,171 @@
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QTimer>
#include <functional>
#include <thread>
namespace chatterino {
namespace {
const QPixmap *getPixmap(const Pixmap &pixmap)
{
if (pixmap.which() == 0)
return boost::get<const QPixmap *>(pixmap);
else
return boost::get<std::unique_ptr<QPixmap>>(pixmap).get();
}
// Frames
Frames::Frames()
{
DebugCount::increase("images");
}
// Frames
Frames::Frames()
{
DebugCount::increase("images");
}
Frames::Frames(const QVector<Frame<QPixmap>> &frames)
: items_(frames)
{
assertInGuiThread();
DebugCount::increase("images");
Frames::Frames(std::vector<Frame> &&frames)
: items_(std::move(frames))
{
DebugCount::increase("images");
if (this->animated()) DebugCount::increase("animated images");
}
if (this->animated()) {
DebugCount::increase("animated images");
Frames::~Frames()
{
DebugCount::decrease("images");
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;
this->gifTimerConnection_ =
getApp()->emotes->gifTimer.signal.connect(
[this] { this->advance(); });
}
}
}
bool Frames::animated() const
{
return this->items_.size() > 1;
}
Frames::~Frames()
{
assertInGuiThread();
DebugCount::decrease("images");
const QPixmap *Frames::current() const
{
if (this->items_.size() == 0) return nullptr;
return getPixmap(this->items_[this->index_].pixmap);
}
if (this->animated()) {
DebugCount::decrease("animated images");
}
const QPixmap *Frames::first() const
{
if (this->items_.size() == 0) return nullptr;
return getPixmap(this->items_.front().pixmap);
}
this->gifTimerConnection_.disconnect();
}
// functions
std::vector<Frame> readFrames(QImageReader &reader, const Url &url)
{
std::vector<Frame> frames;
void Frames::advance()
{
this->durationOffset_ += GIF_FRAME_LENGTH;
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;
}
QImage image;
for (int index = 0; index < reader.imageCount(); ++index) {
if (reader.read(&image)) {
auto pixmap = std::make_unique<QPixmap>(QPixmap::fromImage(image));
// parsed
template <typename Assign>
void assignDelayed(
std::queue<std::pair<Assign, QVector<Frame<QPixmap>>>> &queued,
std::mutex &mutex, std::atomic_bool &loadedEventQueued)
{
std::lock_guard<std::mutex> lock(mutex);
int i = 0;
int duration = std::max(20, reader.nextImageDelay());
frames.push_back(Frame{std::move(pixmap), duration});
while (!queued.empty()) {
queued.front().first(queued.front().second);
queued.pop();
if (++i > 50) {
QTimer::singleShot(3, [&] {
assignDelayed(queued, mutex, loadedEventQueued);
});
return;
}
}
getApp()->windows->forceLayoutChannelViews();
loadedEventQueued = false;
}
if (frames.size() != 0) {
Log("Error while reading image {}: '{}'", url.string,
reader.errorString());
template <typename Assign>
auto makeConvertCallback(const QVector<Frame<QImage>> &parsed,
Assign assign)
{
return [parsed, assign] {
// convert to pixmap
auto frames = QVector<Frame<QPixmap>>();
std::transform(parsed.begin(), parsed.end(),
std::back_inserter(frames), [](auto &frame) {
return Frame<QPixmap>{
QPixmap::fromImage(frame.image),
frame.duration};
});
// put into stack
static std::queue<std::pair<Assign, QVector<Frame<QPixmap>>>>
queued;
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
queued.emplace(assign, frames);
static std::atomic_bool loadedEventQueued{false};
if (!loadedEventQueued) {
loadedEventQueued = true;
QTimer::singleShot(100, [=] {
assignDelayed(queued, mutex, loadedEventQueued);
});
}
};
}
return frames;
}
void queueLoadedEvent()
{
static auto eventQueued = false;
if (!eventQueued) {
eventQueued = true;
QTimer::singleShot(250, [] {
getApp()->windows->incGeneration();
getApp()->windows->layoutChannelViews();
eventQueued = false;
});
}
}
} // namespace
// IMAGE2
std::atomic<bool> Image::loadedEventQueued{false};
ImagePtr Image::fromUrl(const Url &url, qreal scale)
{
static std::unordered_map<Url, std::weak_ptr<Image>> cache;
@ -140,18 +194,13 @@ ImagePtr Image::fromUrl(const Url &url, qreal scale)
if (!shared) {
cache[url] = shared = ImagePtr(new Image(url, scale));
} else {
Warn("same image loaded multiple times: {}", url.string);
// Warn("same image loaded multiple times: {}", url.string);
}
return shared;
}
ImagePtr Image::fromOwningPixmap(std::unique_ptr<QPixmap> pixmap, qreal scale)
{
return ImagePtr(new Image(std::move(pixmap), scale));
}
ImagePtr Image::fromNonOwningPixmap(QPixmap *pixmap, qreal scale)
ImagePtr Image::fromPixmap(const QPixmap &pixmap, qreal scale)
{
return ImagePtr(new Image(pixmap, scale));
}
@ -171,23 +220,15 @@ Image::Image(const Url &url, qreal scale)
: url_(url)
, scale_(scale)
, 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)
, 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
@ -195,7 +236,7 @@ const Url &Image::url() const
return this->url_;
}
const QPixmap *Image::pixmap() const
boost::optional<QPixmap> Image::pixmap() const
{
assertInGuiThread();
@ -204,7 +245,7 @@ const QPixmap *Image::pixmap() const
const_cast<Image *>(this)->load();
}
return this->frames_.current();
return this->frames_->current();
}
qreal Image::scale() const
@ -212,7 +253,7 @@ qreal Image::scale() const
return this->scale_;
}
bool Image::empty() const
bool Image::isEmpty() const
{
return this->empty_;
}
@ -221,14 +262,14 @@ bool Image::animated() const
{
assertInGuiThread();
return this->frames_.animated();
return this->frames_->animated();
}
int Image::width() const
{
assertInGuiThread();
if (auto pixmap = this->frames_.first())
if (auto pixmap = this->frames_->first())
return pixmap->width() * this->scale_;
else
return 16;
@ -238,7 +279,7 @@ int Image::height() const
{
assertInGuiThread();
if (auto pixmap = this->frames_.first())
if (auto pixmap = this->frames_->first())
return pixmap->height() * this->scale_;
else
return 16;
@ -247,39 +288,37 @@ int Image::height() const
void Image::load()
{
NetworkRequest req(this->url().string);
req.setExecuteConcurrently(true);
req.setCaller(&this->object_);
req.setUseQuickLoadCache(true);
req.onSuccess([this, weak = weakOf(this)](auto result) -> Outcome {
assertInGuiThread();
req.onSuccess([that = this, weak = weakOf(this)](auto result) -> Outcome {
auto shared = weak.lock();
if (!shared) return Failure;
auto data = result.getData();
// 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);
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;
});
req.onError([this, weak = weakOf(this)](int) {
auto shared = weak.lock();
if (!shared) return false;
this->frames_ = std::vector<Frame>();
return false;
});
req.execute();
}
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->frames_.first() == other.frames_.first()) return true;
if (this->frames_->first() == other.frames_->first()) return true;
return false;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,9 @@
#include "MessageBuilder.hpp"
#include "common/LinkParser.hpp"
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
#include "providers/twitch/PubsubActions.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Resources.hpp"
#include "singletons/Theme.hpp"
@ -17,7 +20,7 @@ MessagePtr makeSystemMessage(const QString &text)
}
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()
{
this->emplace<TimestampElement>();
this->message().flags.set(MessageFlag::System);
@ -127,6 +131,7 @@ MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
}
MessageBuilder::MessageBuilder(const UnbanAction &action)
: MessageBuilder()
{
this->emplace<TimestampElement>();
this->message().flags.set(MessageFlag::System);
@ -163,7 +168,9 @@ Message &MessageBuilder::message()
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)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,8 +2,12 @@
#include "Application.hpp"
#include "debug/Benchmark.hpp"
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
#include "messages/layouts/MessageLayoutContainer.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include "singletons/WindowManager.hpp"
#include "util/DebugCount.hpp"
@ -24,6 +28,7 @@ namespace chatterino {
MessageLayout::MessageLayout(MessagePtr message)
: message_(message)
, buffer_(nullptr)
, container_(std::make_shared<MessageLayoutContainer>())
{
DebugCount::increase("message layout");
}
@ -41,7 +46,7 @@ const Message *MessageLayout::getMessage()
// Height
int MessageLayout::getHeight() const
{
return container_.getHeight();
return container_->getHeight();
}
// Layout
@ -68,7 +73,7 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags)
// check if work mask changed
layoutRequired |= this->currentWordFlags_ != flags;
this->currentWordFlags_ = flags; // app->settings->getWordTypeMask();
this->currentWordFlags_ = flags; // getSettings()->getWordTypeMask();
// check if layout was requested manually
layoutRequired |= this->flags.has(MessageLayoutFlag::RequiresLayout);
@ -82,9 +87,9 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags)
return false;
}
int oldHeight = this->container_.getHeight();
int oldHeight = this->container_->getHeight();
this->actuallyLayout(width, flags);
if (widthChanged || this->container_.getHeight() != oldHeight) {
if (widthChanged || this->container_->getHeight() != oldHeight) {
this->deleteBuffer();
}
this->invalidateBuffer();
@ -103,22 +108,22 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags _flags)
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) {
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->container_.end();
this->height_ = this->container_.getHeight();
this->container_->end();
this->height_ = this->container_->getHeight();
// collapsed state
this->flags.unset(MessageLayoutFlag::Collapsed);
if (this->container_.isCollapsed()) {
if (this->container_->isCollapsed()) {
this->flags.set(MessageLayoutFlag::Collapsed);
}
}
@ -135,11 +140,12 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
if (!pixmap) {
#ifdef Q_OS_MACOS
pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
int(container_.getHeight() *
int(container_->getHeight() *
painter.device()->devicePixelRatioF()));
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
#else
pixmap = new QPixmap(width, std::max(16, this->container_.getHeight()));
pixmap =
new QPixmap(width, std::max(16, this->container_->getHeight()));
#endif
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);
// draw gif emotes
this->container_.paintAnimatedElements(painter, y);
this->container_->paintAnimatedElements(painter, y);
// draw 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
if (!selection.isEmpty()) {
this->container_.paintSelection(painter, messageIndex, selection, y);
this->container_->paintSelection(painter, messageIndex, selection, y);
}
// draw message seperation line
if (app->settings->separateMessages.getValue()) {
painter.fillRect(0, y, this->container_.getWidth(), 1,
if (getSettings()->separateMessages.getValue()) {
painter.fillRect(0, y, this->container_->getWidth(), 1,
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();
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);
}
@ -208,7 +214,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
backgroundColor = app->themes->messages.backgrounds.highlighted;
} else if (this->message_->flags.has(MessageFlag::Subscription)) {
backgroundColor = app->themes->messages.backgrounds.subscription;
} else if (app->settings->alternateMessageBackground.getValue() &&
} else if (getSettings()->alternateMessageBackground.getValue() &&
this->flags.has(MessageLayoutFlag::AlternateBackground)) {
backgroundColor = app->themes->messages.backgrounds.alternate;
} else {
@ -217,7 +223,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
painter.fillRect(buffer->rect(), backgroundColor);
// draw message
this->container_.paintElements(painter);
this->container_->paintElements(painter);
#ifdef FOURTF
// debug
@ -252,7 +258,7 @@ void MessageLayout::deleteCache()
this->deleteBuffer();
#ifdef XD
this->container_.clear();
this->container_->clear();
#endif
}
@ -265,22 +271,23 @@ void MessageLayout::deleteCache()
const MessageLayoutElement *MessageLayout::getElementAt(QPoint 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
{
return this->container_.getLastCharacterIndex();
return this->container_->getLastCharacterIndex();
}
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

View file

@ -1,19 +1,25 @@
#pragma once
#include "common/Common.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 <boost/noncopyable.hpp>
#include <cinttypes>
#include <memory>
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 {
RequiresBufferUpdate = 1 << 1,
RequiresLayout = 1 << 2,
@ -31,13 +37,10 @@ public:
const Message *getMessage();
// Height
int getHeight() const;
// Flags
MessageLayoutFlags flags;
// Layout
bool layout(int width, float scale_, MessageElementFlags flags);
// Painting
@ -52,7 +55,8 @@ public:
const MessageLayoutElement *getElementAt(QPoint point);
int getLastCharacterIndex() const;
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
bool isDisabled() const;
@ -60,7 +64,7 @@ public:
private:
// variables
MessagePtr message_;
MessageLayoutContainer container_;
std::shared_ptr<MessageLayoutContainer> container_;
std::shared_ptr<QPixmap> buffer_ = nullptr;
bool bufferValid_ = false;

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,9 @@
#include "providers/bttv/BttvEmotes.hpp"
#include "common/Common.hpp"
#include "common/NetworkRequest.hpp"
#include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
#include "messages/ImageSet.hpp"
#include "providers/twitch/TwitchChannel.hpp"
@ -10,11 +12,140 @@
#include <QThread>
namespace chatterino {
namespace {
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)
{
urlTemplate.detach();
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)
return {urlTemplate.replace("{{id}}", id.string)
.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();
@ -22,93 +153,4 @@ Url getEmoteLink(QString urlTemplate, const EmoteId &id,
.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

View file

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

View file

@ -11,78 +11,4 @@
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

View file

@ -6,11 +6,4 @@ class QString;
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

View file

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

View file

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

View file

@ -2,6 +2,7 @@
#include "Application.hpp"
#include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "singletons/Settings.hpp"
#include <rapidjson/error/en.h>
@ -12,82 +13,81 @@
#include <memory>
namespace chatterino {
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,
const rapidjson::Value &unparsedEmoji,
QString shortCode = QString())
{
static uint unicodeBytes[4];
struct {
bool apple;
bool google;
bool twitter;
bool emojione;
bool facebook;
bool messenger;
} capabilities;
struct {
bool apple;
bool google;
bool twitter;
bool emojione;
bool facebook;
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());
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, "unified", emojiData->unifiedCode);
rj::getSafe(unparsedEmoji, "non_qualified",
emojiData->nonQualifiedCode);
rj::getSafe(unparsedEmoji, "unified", emojiData->unifiedCode);
rj::getSafe(unparsedEmoji, "has_img_apple", capabilities.apple);
rj::getSafe(unparsedEmoji, "has_img_google", capabilities.google);
rj::getSafe(unparsedEmoji, "has_img_twitter", capabilities.twitter);
rj::getSafe(unparsedEmoji, "has_img_emojione", capabilities.emojione);
rj::getSafe(unparsedEmoji, "has_img_facebook", capabilities.facebook);
rj::getSafe(unparsedEmoji, "has_img_messenger", capabilities.messenger);
rj::getSafe(unparsedEmoji, "has_img_apple", capabilities.apple);
rj::getSafe(unparsedEmoji, "has_img_google", capabilities.google);
rj::getSafe(unparsedEmoji, "has_img_twitter", capabilities.twitter);
rj::getSafe(unparsedEmoji, "has_img_emojione", capabilities.emojione);
rj::getSafe(unparsedEmoji, "has_img_facebook", capabilities.facebook);
rj::getSafe(unparsedEmoji, "has_img_messenger", capabilities.messenger);
if (capabilities.apple) {
emojiData->capabilities.insert("Apple");
}
if (capabilities.google) {
emojiData->capabilities.insert("Google");
}
if (capabilities.twitter) {
emojiData->capabilities.insert("Twitter");
}
if (capabilities.emojione) {
emojiData->capabilities.insert("EmojiOne 3");
}
if (capabilities.facebook) {
emojiData->capabilities.insert("Facebook");
}
if (capabilities.messenger) {
emojiData->capabilities.insert("Messenger");
}
if (capabilities.apple) {
emojiData->capabilities.insert("Apple");
}
if (capabilities.google) {
emojiData->capabilities.insert("Google");
}
if (capabilities.twitter) {
emojiData->capabilities.insert("Twitter");
}
if (capabilities.emojione) {
emojiData->capabilities.insert("EmojiOne 3");
}
if (capabilities.facebook) {
emojiData->capabilities.insert("Facebook");
}
if (capabilities.messenger) {
emojiData->capabilities.insert("Messenger");
}
QStringList unicodeCharacters;
if (!emojiData->nonQualifiedCode.isEmpty()) {
unicodeCharacters = emojiData->nonQualifiedCode.toLower().split('-');
} else {
unicodeCharacters = emojiData->unifiedCode.toLower().split('-');
QStringList unicodeCharacters;
if (!emojiData->nonQualifiedCode.isEmpty()) {
unicodeCharacters =
emojiData->nonQualifiedCode.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
void Emojis::load()
@ -103,12 +103,10 @@ void Emojis::load()
void Emojis::loadEmojis()
{
std::map<std::string, QString> toneNames;
toneNames["1F3FB"] = "tone1";
toneNames["1F3FC"] = "tone2";
toneNames["1F3FD"] = "tone3";
toneNames["1F3FE"] = "tone4";
toneNames["1F3FF"] = "tone5";
auto toneNames = std::map<std::string, QString>{
{"1F3FB", "tone1"}, {"1F3FC", "tone2"}, {"1F3FD", "tone3"},
{"1F3FE", "tone4"}, {"1F3FF", "tone5"},
};
QFile file(":/emoji.json");
file.open(QFile::ReadOnly);
@ -118,7 +116,7 @@ void Emojis::loadEmojis()
rapidjson::ParseResult result = root.Parse(data.toUtf8(), data.length());
if (result.Code() != rapidjson::kParseErrorNone) {
Log("JSON parse error: {} ({})",
log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset());
return;
}
@ -146,7 +144,7 @@ void Emojis::loadEmojis()
auto toneNameIt = toneNames.find(tone);
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);
continue;
}
@ -218,8 +216,8 @@ void Emojis::loadEmojiSet()
{
auto app = getApp();
app->settings->emojiSet.connect([=](const auto &emojiSet, auto) {
Log("Using emoji set {}", emojiSet);
getSettings()->emojiSet.connect([=](const auto &emojiSet, auto) {
log("Using emoji set {}", emojiSet);
this->emojis.each([=](const auto &name,
std::shared_ptr<EmojiData> &emoji) {
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/"},
// {"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/"},
{"Twitter", "https://pajbot.com/static/emoji/img/twitter/64/"},
{"Facebook", "https://pajbot.com/static/emoji/img/facebook/64/"},
{"Apple", "https://pajbot.com/static/emoji/img/apple/64/"},
{"Google", "https://pajbot.com/static/emoji/img/google/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

View file

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

View file

@ -3,172 +3,164 @@
#include <QJsonArray>
#include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp"
#include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
namespace chatterino {
namespace {
Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale)
{
auto emote = urls.value(emoteScale);
if (emote.isUndefined()) {
return {""};
Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale)
{
auto emote = urls.value(emoteScale);
if (emote.isUndefined()) {
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,
const QString &tooltip, Emote &emoteData)
{
auto url1x = getEmoteLink(urls, "1");
auto url2x = getEmoteLink(urls, "2");
auto url3x = getEmoteLink(urls, "4");
for (auto jsonSet : jsonSets) {
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
//, 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};
}
for (auto jsonEmoteValue : jsonEmotes) {
auto jsonEmote = jsonEmoteValue.toObject();
auto name = EmoteName{jsonEmote.value("name").toString()};
auto id = EmoteId{jsonEmote.value("id").toString()};
auto urls = jsonEmote.value("urls").toObject();
auto emote = Emote();
fillInEmoteData(urls, name,
name.string + "<br/>Global FFZ Emote", emote);
emote.homePage =
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
.arg(id.string)
.arg(name.string)};
emotes[name] =
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
}
}
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
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();
auto it = cache->find(id);
if (it != cache->end()) {
auto shared = it->second.lock();
if (shared) {
return shared;
}
}
return this->global_.get();
}
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;
}
boost::optional<EmotePtr> FfzEmotes::getGlobalEmote(const EmoteName &name)
{
return this->globalEmotes_.access()->get(name);
}
void FfzEmotes::loadGlobalEmotes()
void FfzEmotes::loadEmotes()
{
QString url("https://api.frankerfacez.com/v1/set/global");
NetworkRequest request(url);
request.setCaller(QThread::currentThread());
request.setTimeout(30000);
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();
}
Outcome FfzEmotes::parseGlobalEmotes(const QJsonObject &jsonRoot)
void FfzEmotes::loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback)
{
auto jsonSets = jsonRoot.value("sets").toObject();
auto emotes = this->globalEmotes_.access();
auto replacement = emotes->makeReplacment();
log("[FFZEmotes] Reload FFZ Channel Emotes for channel %s\n", channelName);
for (auto jsonSet : jsonSets) {
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
NetworkRequest request("https://api.frankerfacez.com/v1/room/" +
channelName);
request.setCaller(QThread::currentThread());
request.setTimeout(3000);
for (auto jsonEmoteValue : jsonEmotes) {
auto jsonEmote = jsonEmoteValue.toObject();
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;
});
auto name = EmoteName{jsonEmote.value("name").toString()};
auto id = EmoteId{jsonEmote.value("id").toString()};
auto urls = jsonEmote.value("urls").toObject();
auto emote = Emote();
fillInEmoteData(urls, name, name.string + "<br/>Global FFZ Emote",
emote);
emote.homePage =
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
.arg(id.string)
.arg(name.string)};
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;
request.execute();
}
} // namespace chatterino

View file

@ -1,39 +1,32 @@
#pragma once
#include <memory>
#include "common/UniqueAccess.hpp"
#include "messages/Emote.hpp"
#include "messages/EmoteCache.hpp"
#include "boost/optional.hpp"
#include "common/Aliases.hpp"
#include "common/Atomic.hpp"
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 =
"https://api.frankerfacez.com/v1/set/global";
static constexpr const char *channelEmoteApiUrl =
"https://api.betterttv.net/2/channels/";
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;
boost::optional<EmotePtr> getGlobalEmote(const EmoteName &name);
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_;
private:
Atomic<std::shared_ptr<const EmoteMap>> global_;
};
} // namespace chatterino

View file

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

View file

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

View file

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

View file

@ -1,5 +1,6 @@
#include "providers/twitch/PartialTwitchUser.hpp"
#include "common/Common.hpp"
#include "common/NetworkRequest.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchCommon.hpp"
@ -42,23 +43,23 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
request.onSuccess([successCallback](auto result) -> Outcome {
auto root = result.parseJson();
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;
}
auto users = root.value("users").toArray();
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;
}
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;
}
auto firstUser = users[0].toObject();
auto id = firstUser.value("_id");
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 "
"string");
return Failure;

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