Merge pull request #1299 from leon-richardt/normalize-line-endings

Normalize Line Endings
This commit is contained in:
fourtf 2019-09-08 23:52:55 +02:00 committed by GitHub
commit 6cd3cfe79f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
158 changed files with 15175 additions and 15173 deletions

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
# Normalize line endings to LF for files that Git detects as text
* text=auto

View file

@ -1,59 +1,59 @@
version: "{build}" version: "{build}"
branches: branches:
only: only:
- master - master
image: Visual Studio 2017 image: Visual Studio 2017
platform: Any CPU platform: Any CPU
clone_depth: 1 clone_depth: 1
init: init:
- cmd: '' - cmd: ''
install: install:
- cmd: >- - cmd: >-
git submodule update --init --recursive git submodule update --init --recursive
set QTDIR=C:\Qt\5.11\msvc2017_64 set QTDIR=C:\Qt\5.11\msvc2017_64
set PATH=%PATH%;%QTDIR%\bin set PATH=%PATH%;%QTDIR%\bin
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
pip install conan -q pip install conan -q
build_script: build_script:
- cmd: >- - cmd: >-
dir dir
mkdir build mkdir build
cd build cd build
conan install .. conan install ..
set dateOfBuild=%date:~7,2%.%date:~4,2%.%date:~10,4% set dateOfBuild=%date:~7,2%.%date:~4,2%.%date:~10,4%
qmake ../chatterino.pro DEFINES+="CHATTERINO_NIGHTLY_VERSION_STRING=\\\"'$s%dateOfBuild% '$$system(git describe --always)-$$system(git rev-list master --count)\\\"" qmake ../chatterino.pro DEFINES+="CHATTERINO_NIGHTLY_VERSION_STRING=\\\"'$s%dateOfBuild% '$$system(git describe --always)-$$system(git rev-list master --count)\\\""
set cl=/MP set cl=/MP
nmake /S /NOLOGO nmake /S /NOLOGO
windeployqt release/chatterino.exe --release --no-compiler-runtime --no-translations --no-opengl-sw --dir Chatterino2/ windeployqt release/chatterino.exe --release --no-compiler-runtime --no-translations --no-opengl-sw --dir Chatterino2/
cp release/chatterino.exe Chatterino2/ cp release/chatterino.exe Chatterino2/
7z a chatterino-windows-x86-64.zip Chatterino2/ 7z a chatterino-windows-x86-64.zip Chatterino2/
artifacts: artifacts:
- path: build/chatterino-windows-x86-64.zip - path: build/chatterino-windows-x86-64.zip
name: chatterino name: chatterino
deploy: deploy:
- provider: GitHub - provider: GitHub
tag: nightly-build tag: nightly-build
release: nightly-build release: nightly-build
description: 'nightly v$(appveyor_build_version) built $(APPVEYOR_REPO_COMMIT_TIMESTAMP)\nLast change: $(APPVEYOR_REPO_COMMIT_MESSAGE) \n$(APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED)' description: 'nightly v$(appveyor_build_version) built $(APPVEYOR_REPO_COMMIT_TIMESTAMP)\nLast change: $(APPVEYOR_REPO_COMMIT_MESSAGE) \n$(APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED)'
auth_token: auth_token:
secure: sAJzAbiQSsYZLT+byDar9u61X0E9o35anaPMSFkOzdHeDFHjx1kW4cDP/4EEbxhx secure: sAJzAbiQSsYZLT+byDar9u61X0E9o35anaPMSFkOzdHeDFHjx1kW4cDP/4EEbxhx
repository: Chatterino/chatterino2 repository: Chatterino/chatterino2
artifact: build/chatterino-windows-x86-64.zip artifact: build/chatterino-windows-x86-64.zip
prerelease: true prerelease: true
force_update: true force_update: true
on: on:
branch: master branch: master

View file

@ -1,12 +1,12 @@
[requires] [requires]
OpenSSL/1.0.2o@conan/stable OpenSSL/1.0.2o@conan/stable
boost/1.69.0@conan/stable boost/1.69.0@conan/stable
[generators] [generators]
qmake qmake
[options] [options]
OpenSSL:shared=True OpenSSL:shared=True
[imports] [imports]
bin, *.dll -> ./Chatterino2 @ keep_path=False bin, *.dll -> ./Chatterino2 @ keep_path=False

View file

@ -1,128 +1,128 @@
#include "BaseSettings.hpp" #include "BaseSettings.hpp"
#include <QDebug> #include <QDebug>
#include "util/Clamp.hpp" #include "util/Clamp.hpp"
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
std::vector<std::weak_ptr<pajlada::Settings::SettingData>> _settings; std::vector<std::weak_ptr<pajlada::Settings::SettingData>> _settings;
AB_SETTINGS_CLASS *AB_SETTINGS_CLASS::instance = nullptr; AB_SETTINGS_CLASS *AB_SETTINGS_CLASS::instance = nullptr;
void _actuallyRegisterSetting( void _actuallyRegisterSetting(
std::weak_ptr<pajlada::Settings::SettingData> setting) std::weak_ptr<pajlada::Settings::SettingData> setting)
{ {
_settings.push_back(setting); _settings.push_back(setting);
} }
AB_SETTINGS_CLASS::AB_SETTINGS_CLASS(const QString &settingsDirectory) AB_SETTINGS_CLASS::AB_SETTINGS_CLASS(const QString &settingsDirectory)
{ {
AB_SETTINGS_CLASS::instance = this; AB_SETTINGS_CLASS::instance = this;
QString settingsPath = settingsDirectory + "/settings.json"; QString settingsPath = settingsDirectory + "/settings.json";
// get global instance of the settings library // get global instance of the settings library
auto settingsInstance = pajlada::Settings::SettingManager::getInstance(); auto settingsInstance = pajlada::Settings::SettingManager::getInstance();
settingsInstance->load(qPrintable(settingsPath)); settingsInstance->load(qPrintable(settingsPath));
settingsInstance->setBackupEnabled(true); settingsInstance->setBackupEnabled(true);
settingsInstance->setBackupSlots(9); settingsInstance->setBackupSlots(9);
settingsInstance->saveMethod = settingsInstance->saveMethod =
pajlada::Settings::SettingManager::SaveMethod::SaveOnExit; pajlada::Settings::SettingManager::SaveMethod::SaveOnExit;
} }
void AB_SETTINGS_CLASS::saveSnapshot() void AB_SETTINGS_CLASS::saveSnapshot()
{ {
rapidjson::Document *d = new rapidjson::Document(rapidjson::kObjectType); rapidjson::Document *d = new rapidjson::Document(rapidjson::kObjectType);
rapidjson::Document::AllocatorType &a = d->GetAllocator(); rapidjson::Document::AllocatorType &a = d->GetAllocator();
for (const auto &weakSetting : _settings) for (const auto &weakSetting : _settings)
{ {
auto setting = weakSetting.lock(); auto setting = weakSetting.lock();
if (!setting) if (!setting)
{ {
continue; continue;
} }
rapidjson::Value key(setting->getPath().c_str(), a); rapidjson::Value key(setting->getPath().c_str(), a);
auto curVal = setting->unmarshalJSON(); auto curVal = setting->unmarshalJSON();
if (curVal == nullptr) if (curVal == nullptr)
{ {
continue; continue;
} }
rapidjson::Value val; rapidjson::Value val;
val.CopyFrom(*curVal, a); val.CopyFrom(*curVal, a);
d->AddMember(key.Move(), val.Move(), a); d->AddMember(key.Move(), val.Move(), a);
} }
// log("Snapshot state: {}", rj::stringify(*d)); // log("Snapshot state: {}", rj::stringify(*d));
this->snapshot_.reset(d); this->snapshot_.reset(d);
} }
void AB_SETTINGS_CLASS::restoreSnapshot() void AB_SETTINGS_CLASS::restoreSnapshot()
{ {
if (!this->snapshot_) if (!this->snapshot_)
{ {
return; return;
} }
const auto &snapshot = *(this->snapshot_.get()); const auto &snapshot = *(this->snapshot_.get());
if (!snapshot.IsObject()) if (!snapshot.IsObject())
{ {
return; return;
} }
for (const auto &weakSetting : _settings) for (const auto &weakSetting : _settings)
{ {
auto setting = weakSetting.lock(); auto setting = weakSetting.lock();
if (!setting) if (!setting)
{ {
continue; continue;
} }
const char *path = setting->getPath().c_str(); const char *path = setting->getPath().c_str();
if (!snapshot.HasMember(path)) if (!snapshot.HasMember(path))
{ {
continue; continue;
} }
setting->marshalJSON(snapshot[path]); setting->marshalJSON(snapshot[path]);
} }
} }
float AB_SETTINGS_CLASS::getClampedUiScale() const float AB_SETTINGS_CLASS::getClampedUiScale() const
{ {
return clamp<float>(this->uiScale.getValue(), 0.2f, 10); return clamp<float>(this->uiScale.getValue(), 0.2f, 10);
} }
void AB_SETTINGS_CLASS::setClampedUiScale(float value) void AB_SETTINGS_CLASS::setClampedUiScale(float value)
{ {
this->uiScale.setValue(clamp<float>(value, 0.2f, 10)); this->uiScale.setValue(clamp<float>(value, 0.2f, 10));
} }
#ifndef AB_CUSTOM_SETTINGS #ifndef AB_CUSTOM_SETTINGS
Settings *getSettings() Settings *getSettings()
{ {
static_assert(std::is_same_v<AB_SETTINGS_CLASS, Settings>, static_assert(std::is_same_v<AB_SETTINGS_CLASS, Settings>,
"`AB_SETTINGS_CLASS` must be the same as `Settings`"); "`AB_SETTINGS_CLASS` must be the same as `Settings`");
assert(AB_SETTINGS_CLASS::instance); assert(AB_SETTINGS_CLASS::instance);
return AB_SETTINGS_CLASS::instance; return AB_SETTINGS_CLASS::instance;
} }
#endif #endif
AB_SETTINGS_CLASS *getABSettings() AB_SETTINGS_CLASS *getABSettings()
{ {
assert(AB_SETTINGS_CLASS::instance); assert(AB_SETTINGS_CLASS::instance);
return AB_SETTINGS_CLASS::instance; return AB_SETTINGS_CLASS::instance;
} }
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE

View file

@ -1,52 +1,52 @@
#ifndef AB_SETTINGS_H #ifndef AB_SETTINGS_H
#define AB_SETTINGS_H #define AB_SETTINGS_H
#include <rapidjson/document.h> #include <rapidjson/document.h>
#include <QString> #include <QString>
#include <memory> #include <memory>
#include <pajlada/settings/settingdata.hpp> #include <pajlada/settings/settingdata.hpp>
#include "common/ChatterinoSetting.hpp" #include "common/ChatterinoSetting.hpp"
#ifdef AB_CUSTOM_SETTINGS #ifdef AB_CUSTOM_SETTINGS
# define AB_SETTINGS_CLASS ABSettings # define AB_SETTINGS_CLASS ABSettings
#else #else
# define AB_SETTINGS_CLASS Settings # define AB_SETTINGS_CLASS Settings
#endif #endif
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
class Settings; class Settings;
void _actuallyRegisterSetting( void _actuallyRegisterSetting(
std::weak_ptr<pajlada::Settings::SettingData> setting); std::weak_ptr<pajlada::Settings::SettingData> setting);
class AB_SETTINGS_CLASS class AB_SETTINGS_CLASS
{ {
public: public:
AB_SETTINGS_CLASS(const QString &settingsDirectory); AB_SETTINGS_CLASS(const QString &settingsDirectory);
void saveSnapshot(); void saveSnapshot();
void restoreSnapshot(); void restoreSnapshot();
static AB_SETTINGS_CLASS *instance; static AB_SETTINGS_CLASS *instance;
FloatSetting uiScale = {"/appearance/uiScale2", 1}; FloatSetting uiScale = {"/appearance/uiScale2", 1};
BoolSetting windowTopMost = {"/appearance/windowAlwaysOnTop", false}; BoolSetting windowTopMost = {"/appearance/windowAlwaysOnTop", false};
float getClampedUiScale() const; float getClampedUiScale() const;
void setClampedUiScale(float value); void setClampedUiScale(float value);
private: private:
std::unique_ptr<rapidjson::Document> snapshot_; std::unique_ptr<rapidjson::Document> snapshot_;
}; };
Settings *getSettings(); Settings *getSettings();
AB_SETTINGS_CLASS *getABSettings(); AB_SETTINGS_CLASS *getABSettings();
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE
#ifdef CHATTERINO #ifdef CHATTERINO
# include "singletons/Settings.hpp" # include "singletons/Settings.hpp"
#endif #endif
#endif #endif

View file

@ -1,225 +1,225 @@
#include "BaseTheme.hpp" #include "BaseTheme.hpp"
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
namespace { namespace {
double getMultiplierByTheme(const QString &themeName) double getMultiplierByTheme(const QString &themeName)
{ {
if (themeName == "Light") if (themeName == "Light")
{ {
return 0.8; return 0.8;
} }
else if (themeName == "White") else if (themeName == "White")
{ {
return 1.0; return 1.0;
} }
else if (themeName == "Black") else if (themeName == "Black")
{ {
return -1.0; return -1.0;
} }
else if (themeName == "Dark") else if (themeName == "Dark")
{ {
return -0.8; return -0.8;
} }
/* /*
else if (themeName == "Custom") else if (themeName == "Custom")
{ {
return getSettings()->customThemeMultiplier.getValue(); return getSettings()->customThemeMultiplier.getValue();
} }
*/ */
return -0.8; return -0.8;
} }
} // namespace } // namespace
bool AB_THEME_CLASS::isLightTheme() const bool AB_THEME_CLASS::isLightTheme() const
{ {
return this->isLight_; return this->isLight_;
} }
void AB_THEME_CLASS::update() void AB_THEME_CLASS::update()
{ {
this->actuallyUpdate(this->themeHue, this->actuallyUpdate(this->themeHue,
getMultiplierByTheme(this->themeName.getValue())); getMultiplierByTheme(this->themeName.getValue()));
this->updated.invoke(); this->updated.invoke();
} }
void AB_THEME_CLASS::actuallyUpdate(double hue, double multiplier) void AB_THEME_CLASS::actuallyUpdate(double hue, double multiplier)
{ {
this->isLight_ = multiplier > 0; this->isLight_ = multiplier > 0;
bool lightWin = isLight_; bool lightWin = isLight_;
// QColor themeColor = QColor::fromHslF(hue, 0.43, 0.5); // QColor themeColor = QColor::fromHslF(hue, 0.43, 0.5);
QColor themeColor = QColor::fromHslF(hue, 0.8, 0.5); QColor themeColor = QColor::fromHslF(hue, 0.8, 0.5);
QColor themeColorNoSat = QColor::fromHslF(hue, 0, 0.5); QColor themeColorNoSat = QColor::fromHslF(hue, 0, 0.5);
qreal sat = 0; qreal sat = 0;
// 0.05; // 0.05;
auto getColor = [multiplier](double h, double s, double l, double a = 1.0) { auto getColor = [multiplier](double h, double s, double l, double a = 1.0) {
return QColor::fromHslF(h, s, ((l - 0.5) * multiplier) + 0.5, a); return QColor::fromHslF(h, s, ((l - 0.5) * multiplier) + 0.5, a);
}; };
/// WINDOW /// WINDOW
{ {
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
this->window.background = lightWin ? "#fff" : QColor(61, 60, 56); this->window.background = lightWin ? "#fff" : QColor(61, 60, 56);
#else #else
this->window.background = lightWin ? "#fff" : "#111"; this->window.background = lightWin ? "#fff" : "#111";
#endif #endif
QColor fg = this->window.text = lightWin ? "#000" : "#eee"; QColor fg = this->window.text = lightWin ? "#000" : "#eee";
this->window.borderFocused = lightWin ? "#ccc" : themeColor; this->window.borderFocused = lightWin ? "#ccc" : themeColor;
this->window.borderUnfocused = lightWin ? "#ccc" : themeColorNoSat; this->window.borderUnfocused = lightWin ? "#ccc" : themeColorNoSat;
// Ubuntu style // Ubuntu style
// TODO: add setting for this // TODO: add setting for this
// TabText = QColor(210, 210, 210); // TabText = QColor(210, 210, 210);
// TabBackground = QColor(61, 60, 56); // TabBackground = QColor(61, 60, 56);
// TabHoverText = QColor(210, 210, 210); // TabHoverText = QColor(210, 210, 210);
// TabHoverBackground = QColor(73, 72, 68); // TabHoverBackground = QColor(73, 72, 68);
// message (referenced later) // message (referenced later)
this->messages.textColors.caret = // this->messages.textColors.caret = //
this->messages.textColors.regular = isLight_ ? "#000" : "#fff"; this->messages.textColors.regular = isLight_ ? "#000" : "#fff";
QColor highlighted = lightWin ? QColor("#ff0000") : QColor("#ee6166"); QColor highlighted = lightWin ? QColor("#ff0000") : QColor("#ee6166");
/// TABS /// TABS
if (lightWin) if (lightWin)
{ {
this->tabs.regular = { this->tabs.regular = {
QColor("#444"), QColor("#444"),
{QColor("#fff"), QColor("#eee"), QColor("#fff")}, {QColor("#fff"), QColor("#eee"), QColor("#fff")},
{QColor("#fff"), QColor("#fff"), QColor("#fff")}}; {QColor("#fff"), QColor("#fff"), QColor("#fff")}};
this->tabs.newMessage = { this->tabs.newMessage = {
QColor("#222"), QColor("#222"),
{QColor("#fff"), QColor("#eee"), QColor("#fff")}, {QColor("#fff"), QColor("#eee"), QColor("#fff")},
{QColor("#bbb"), QColor("#bbb"), QColor("#bbb")}}; {QColor("#bbb"), QColor("#bbb"), QColor("#bbb")}};
this->tabs.highlighted = { this->tabs.highlighted = {
fg, fg,
{QColor("#fff"), QColor("#eee"), QColor("#fff")}, {QColor("#fff"), QColor("#eee"), QColor("#fff")},
{highlighted, highlighted, highlighted}}; {highlighted, highlighted, highlighted}};
this->tabs.selected = { this->tabs.selected = {
QColor("#000"), QColor("#000"),
{QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")}, {QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")},
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}}; {QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
} }
else else
{ {
this->tabs.regular = { this->tabs.regular = {
QColor("#aaa"), QColor("#aaa"),
{QColor("#252525"), QColor("#252525"), QColor("#252525")}, {QColor("#252525"), QColor("#252525"), QColor("#252525")},
{QColor("#444"), QColor("#444"), QColor("#444")}}; {QColor("#444"), QColor("#444"), QColor("#444")}};
this->tabs.newMessage = { this->tabs.newMessage = {
fg, fg,
{QColor("#252525"), QColor("#252525"), QColor("#252525")}, {QColor("#252525"), QColor("#252525"), QColor("#252525")},
{QColor("#888"), QColor("#888"), QColor("#888")}}; {QColor("#888"), QColor("#888"), QColor("#888")}};
this->tabs.highlighted = { this->tabs.highlighted = {
fg, fg,
{QColor("#252525"), QColor("#252525"), QColor("#252525")}, {QColor("#252525"), QColor("#252525"), QColor("#252525")},
{highlighted, highlighted, highlighted}}; {highlighted, highlighted, highlighted}};
this->tabs.selected = { this->tabs.selected = {
QColor("#fff"), QColor("#fff"),
{QColor("#555555"), QColor("#555555"), QColor("#555555")}, {QColor("#555555"), QColor("#555555"), QColor("#555555")},
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}}; {QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
} }
// scrollbar // scrollbar
this->scrollbars.highlights.highlight = QColor("#ee6166"); this->scrollbars.highlights.highlight = QColor("#ee6166");
this->scrollbars.highlights.subscription = QColor("#C466FF"); this->scrollbars.highlights.subscription = QColor("#C466FF");
// this->tabs.newMessage = { // this->tabs.newMessage = {
// fg, // fg,
// {QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern), // {QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
// QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern), // QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
// QBrush(blendColors(themeColorNoSat, "#ccc", 0.9), // QBrush(blendColors(themeColorNoSat, "#ccc", 0.9),
// Qt::FDiagPattern)}}; // Qt::FDiagPattern)}};
// this->tabs.newMessage = { // this->tabs.newMessage = {
// fg, // fg,
// {QBrush(blendColors(themeColor, "#666", 0.7), // {QBrush(blendColors(themeColor, "#666", 0.7),
// Qt::FDiagPattern), // Qt::FDiagPattern),
// QBrush(blendColors(themeColor, "#666", 0.5), // QBrush(blendColors(themeColor, "#666", 0.5),
// Qt::FDiagPattern), // Qt::FDiagPattern),
// QBrush(blendColors(themeColorNoSat, "#666", 0.7), // QBrush(blendColors(themeColorNoSat, "#666", 0.7),
// Qt::FDiagPattern)}}; // Qt::FDiagPattern)}};
// this->tabs.highlighted = {fg, {QColor("#777"), // this->tabs.highlighted = {fg, {QColor("#777"),
// QColor("#777"), QColor("#666")}}; // QColor("#777"), QColor("#666")}};
this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color(); this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color();
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE
// Split // Split
bool flat = isLight_; bool flat = isLight_;
// Message // Message
this->messages.textColors.link = this->messages.textColors.link =
isLight_ ? QColor(66, 134, 244) : QColor(66, 134, 244); isLight_ ? QColor(66, 134, 244) : QColor(66, 134, 244);
this->messages.textColors.system = QColor(140, 127, 127); this->messages.textColors.system = QColor(140, 127, 127);
this->messages.backgrounds.regular = getColor(0, sat, 1); this->messages.backgrounds.regular = getColor(0, sat, 1);
this->messages.backgrounds.alternate = getColor(0, sat, 0.96); this->messages.backgrounds.alternate = getColor(0, sat, 0.96);
if (isLight_) if (isLight_)
{ {
this->messages.backgrounds.highlighted = this->messages.backgrounds.highlighted =
blendColors(themeColor, this->messages.backgrounds.regular, 0.8); blendColors(themeColor, this->messages.backgrounds.regular, 0.8);
} }
else else
{ {
// REMOVED // REMOVED
// this->messages.backgrounds.highlighted = // this->messages.backgrounds.highlighted =
// QColor(getSettings()->highlightColor); // QColor(getSettings()->highlightColor);
} }
this->messages.backgrounds.subscription = this->messages.backgrounds.subscription =
blendColors(QColor("#C466FF"), this->messages.backgrounds.regular, 0.7); blendColors(QColor("#C466FF"), this->messages.backgrounds.regular, 0.7);
// this->messages.backgrounds.resub // this->messages.backgrounds.resub
// this->messages.backgrounds.whisper // this->messages.backgrounds.whisper
this->messages.disabled = getColor(0, sat, 1, 0.6); this->messages.disabled = getColor(0, sat, 1, 0.6);
// this->messages.seperator = // this->messages.seperator =
// this->messages.seperatorInner = // this->messages.seperatorInner =
// Scrollbar // Scrollbar
this->scrollbars.background = QColor(0, 0, 0, 0); this->scrollbars.background = QColor(0, 0, 0, 0);
// this->scrollbars.background = splits.background; // this->scrollbars.background = splits.background;
// this->scrollbars.background.setAlphaF(qreal(0.2)); // this->scrollbars.background.setAlphaF(qreal(0.2));
this->scrollbars.thumb = getColor(0, sat, 0.70); this->scrollbars.thumb = getColor(0, sat, 0.70);
this->scrollbars.thumbSelected = getColor(0, sat, 0.65); this->scrollbars.thumbSelected = getColor(0, sat, 0.65);
// tooltip // tooltip
this->tooltip.background = QColor(0, 0, 0); this->tooltip.background = QColor(0, 0, 0);
this->tooltip.text = QColor(255, 255, 255); this->tooltip.text = QColor(255, 255, 255);
// Selection // Selection
this->messages.selection = this->messages.selection =
isLightTheme() ? QColor(0, 0, 0, 64) : QColor(255, 255, 255, 64); isLightTheme() ? QColor(0, 0, 0, 64) : QColor(255, 255, 255, 64);
} }
QColor AB_THEME_CLASS::blendColors(const QColor &color1, const QColor &color2, QColor AB_THEME_CLASS::blendColors(const QColor &color1, const QColor &color2,
qreal ratio) qreal ratio)
{ {
int r = int(color1.red() * (1 - ratio) + color2.red() * ratio); int r = int(color1.red() * (1 - ratio) + color2.red() * ratio);
int g = int(color1.green() * (1 - ratio) + color2.green() * ratio); int g = int(color1.green() * (1 - ratio) + color2.green() * ratio);
int b = int(color1.blue() * (1 - ratio) + color2.blue() * ratio); int b = int(color1.blue() * (1 - ratio) + color2.blue() * ratio);
return QColor(r, g, b, 255); return QColor(r, g, b, 255);
} }
#ifndef AB_CUSTOM_THEME #ifndef AB_CUSTOM_THEME
Theme *getTheme() Theme *getTheme()
{ {
static auto theme = [] { static auto theme = [] {
auto theme = new Theme(); auto theme = new Theme();
theme->update(); theme->update();
return theme; return theme;
}(); }();
return theme; return theme;
} }
#endif #endif
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE

View file

@ -1,117 +1,117 @@
#ifndef AB_THEME_H #ifndef AB_THEME_H
#define AB_THEME_H #define AB_THEME_H
#include <QBrush> #include <QBrush>
#include <QColor> #include <QColor>
#include <common/ChatterinoSetting.hpp> #include <common/ChatterinoSetting.hpp>
#ifdef AB_CUSTOM_THEME #ifdef AB_CUSTOM_THEME
# define AB_THEME_CLASS BaseTheme # define AB_THEME_CLASS BaseTheme
#else #else
# define AB_THEME_CLASS Theme # define AB_THEME_CLASS Theme
#endif #endif
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
class Theme; class Theme;
class AB_THEME_CLASS class AB_THEME_CLASS
{ {
public: public:
bool isLightTheme() const; bool isLightTheme() const;
struct TabColors { struct TabColors {
QColor text; QColor text;
struct { struct {
QBrush regular; QBrush regular;
QBrush hover; QBrush hover;
QBrush unfocused; QBrush unfocused;
} backgrounds; } backgrounds;
struct { struct {
QColor regular; QColor regular;
QColor hover; QColor hover;
QColor unfocused; QColor unfocused;
} line; } line;
}; };
/// WINDOW /// WINDOW
struct { struct {
QColor background; QColor background;
QColor text; QColor text;
QColor borderUnfocused; QColor borderUnfocused;
QColor borderFocused; QColor borderFocused;
} window; } window;
/// TABS /// TABS
struct { struct {
TabColors regular; TabColors regular;
TabColors newMessage; TabColors newMessage;
TabColors highlighted; TabColors highlighted;
TabColors selected; TabColors selected;
QColor border; QColor border;
QColor bottomLine; QColor bottomLine;
} tabs; } tabs;
/// MESSAGES /// MESSAGES
struct { struct {
struct { struct {
QColor regular; QColor regular;
QColor caret; QColor caret;
QColor link; QColor link;
QColor system; QColor system;
} textColors; } textColors;
struct { struct {
QColor regular; QColor regular;
QColor alternate; QColor alternate;
QColor highlighted; QColor highlighted;
QColor subscription; QColor subscription;
// QColor whisper; // QColor whisper;
} backgrounds; } backgrounds;
QColor disabled; QColor disabled;
// QColor seperator; // QColor seperator;
// QColor seperatorInner; // QColor seperatorInner;
QColor selection; QColor selection;
} messages; } messages;
/// SCROLLBAR /// SCROLLBAR
struct { struct {
QColor background; QColor background;
QColor thumb; QColor thumb;
QColor thumbSelected; QColor thumbSelected;
struct { struct {
QColor highlight; QColor highlight;
QColor subscription; QColor subscription;
} highlights; } highlights;
} scrollbars; } scrollbars;
/// TOOLTIP /// TOOLTIP
struct { struct {
QColor text; QColor text;
QColor background; QColor background;
} tooltip; } tooltip;
void update(); void update();
virtual void actuallyUpdate(double hue, double multiplier); virtual void actuallyUpdate(double hue, double multiplier);
QColor blendColors(const QColor &color1, const QColor &color2, qreal ratio); QColor blendColors(const QColor &color1, const QColor &color2, qreal ratio);
pajlada::Signals::NoArgSignal updated; pajlada::Signals::NoArgSignal updated;
QStringSetting themeName{"/appearance/theme/name", "Dark"}; QStringSetting themeName{"/appearance/theme/name", "Dark"};
DoubleSetting themeHue{"/appearance/theme/hue", 0.0}; DoubleSetting themeHue{"/appearance/theme/hue", 0.0};
private: private:
bool isLight_ = false; bool isLight_ = false;
}; };
// Implemented in parent project if AB_CUSTOM_THEME is set. // Implemented in parent project if AB_CUSTOM_THEME is set.
// Otherwise implemented in BaseThemecpp // Otherwise implemented in BaseThemecpp
Theme *getTheme(); Theme *getTheme();
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE
#ifdef CHATTERINO #ifdef CHATTERINO
# include "singletons/Theme.hpp" # include "singletons/Theme.hpp"
#endif #endif
#endif #endif

View file

@ -1,54 +1,54 @@
#pragma once #pragma once
#include <QString> #include <QString>
#include <pajlada/settings.hpp> #include <pajlada/settings.hpp>
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
void _registerSetting(std::weak_ptr<pajlada::Settings::SettingData> setting); void _registerSetting(std::weak_ptr<pajlada::Settings::SettingData> setting);
template <typename Type> template <typename Type>
class ChatterinoSetting : public pajlada::Settings::Setting<Type> class ChatterinoSetting : public pajlada::Settings::Setting<Type>
{ {
public: public:
ChatterinoSetting(const std::string &path) ChatterinoSetting(const std::string &path)
: pajlada::Settings::Setting<Type>(path) : pajlada::Settings::Setting<Type>(path)
{ {
_registerSetting(this->getData()); _registerSetting(this->getData());
} }
ChatterinoSetting(const std::string &path, const Type &defaultValue) ChatterinoSetting(const std::string &path, const Type &defaultValue)
: pajlada::Settings::Setting<Type>(path, defaultValue) : pajlada::Settings::Setting<Type>(path, defaultValue)
{ {
_registerSetting(this->getData()); _registerSetting(this->getData());
} }
template <typename T2> template <typename T2>
ChatterinoSetting &operator=(const T2 &newValue) ChatterinoSetting &operator=(const T2 &newValue)
{ {
this->setValue(newValue); this->setValue(newValue);
return *this; return *this;
} }
ChatterinoSetting &operator=(Type &&newValue) noexcept ChatterinoSetting &operator=(Type &&newValue) noexcept
{ {
pajlada::Settings::Setting<Type>::operator=(newValue); pajlada::Settings::Setting<Type>::operator=(newValue);
return *this; return *this;
} }
using pajlada::Settings::Setting<Type>::operator==; using pajlada::Settings::Setting<Type>::operator==;
using pajlada::Settings::Setting<Type>::operator!=; using pajlada::Settings::Setting<Type>::operator!=;
using pajlada::Settings::Setting<Type>::operator Type; using pajlada::Settings::Setting<Type>::operator Type;
}; };
using BoolSetting = ChatterinoSetting<bool>; using BoolSetting = ChatterinoSetting<bool>;
using FloatSetting = ChatterinoSetting<float>; using FloatSetting = ChatterinoSetting<float>;
using DoubleSetting = ChatterinoSetting<double>; using DoubleSetting = ChatterinoSetting<double>;
using IntSetting = ChatterinoSetting<int>; using IntSetting = ChatterinoSetting<int>;
using StringSetting = ChatterinoSetting<std::string>; using StringSetting = ChatterinoSetting<std::string>;
using QStringSetting = ChatterinoSetting<QString>; using QStringSetting = ChatterinoSetting<QString>;
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE

View file

@ -1,82 +1,82 @@
#pragma once #pragma once
#include <type_traits> #include <type_traits>
namespace chatterino { namespace chatterino {
template <typename T, typename Q = typename std::underlying_type<T>::type> template <typename T, typename Q = typename std::underlying_type<T>::type>
class FlagsEnum class FlagsEnum
{ {
public: public:
FlagsEnum() FlagsEnum()
: value_(static_cast<T>(0)) : value_(static_cast<T>(0))
{ {
} }
FlagsEnum(T value) FlagsEnum(T value)
: value_(value) : value_(value)
{ {
} }
FlagsEnum(std::initializer_list<T> flags) FlagsEnum(std::initializer_list<T> flags)
{ {
for (auto flag : flags) for (auto flag : flags)
{ {
this->set(flag); this->set(flag);
} }
} }
bool operator==(const FlagsEnum<T> &other) bool operator==(const FlagsEnum<T> &other)
{ {
return this->value_ == other.value_; return this->value_ == other.value_;
} }
bool operator!=(const FlagsEnum &other) bool operator!=(const FlagsEnum &other)
{ {
return this->value_ != other.value_; return this->value_ != other.value_;
} }
void set(T flag) void set(T flag)
{ {
reinterpret_cast<Q &>(this->value_) |= static_cast<Q>(flag); reinterpret_cast<Q &>(this->value_) |= static_cast<Q>(flag);
} }
void unset(T flag) void unset(T flag)
{ {
reinterpret_cast<Q &>(this->value_) &= ~static_cast<Q>(flag); reinterpret_cast<Q &>(this->value_) &= ~static_cast<Q>(flag);
} }
void set(T flag, bool value) void set(T flag, bool value)
{ {
if (value) if (value)
this->set(flag); this->set(flag);
else else
this->unset(flag); this->unset(flag);
} }
bool has(T flag) const bool has(T flag) const
{ {
return static_cast<Q>(this->value_) & static_cast<Q>(flag); return static_cast<Q>(this->value_) & static_cast<Q>(flag);
} }
bool hasAny(FlagsEnum flags) const bool hasAny(FlagsEnum flags) const
{ {
return static_cast<Q>(this->value_) & static_cast<Q>(flags.value_); return static_cast<Q>(this->value_) & static_cast<Q>(flags.value_);
} }
bool hasAll(FlagsEnum<T> flags) const bool hasAll(FlagsEnum<T> flags) const
{ {
return (static_cast<Q>(this->value_) & static_cast<Q>(flags.value_)) && return (static_cast<Q>(this->value_) & static_cast<Q>(flags.value_)) &&
static_cast<Q>(flags->value); static_cast<Q>(flags->value);
} }
bool hasNone(std::initializer_list<T> flags) const bool hasNone(std::initializer_list<T> flags) const
{ {
return !this->hasAny(flags); return !this->hasAny(flags);
} }
private: private:
T value_{}; T value_{};
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,26 +1,26 @@
#pragma once #pragma once
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
class Settings; class Settings;
class Paths; class Paths;
class Singleton : boost::noncopyable class Singleton : boost::noncopyable
{ {
public: public:
virtual ~Singleton() = default; virtual ~Singleton() = default;
virtual void initialize(Settings &settings, Paths &paths) virtual void initialize(Settings &settings, Paths &paths)
{ {
(void)(settings); (void)(settings);
(void)(paths); (void)(paths);
} }
virtual void save() virtual void save()
{ {
} }
}; };
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE

View file

@ -1,21 +1,21 @@
#pragma once #pragma once
#include <QCoreApplication> #include <QCoreApplication>
#include <QThread> #include <QThread>
#include <cassert> #include <cassert>
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
static bool isGuiThread() static bool isGuiThread()
{ {
return QCoreApplication::instance()->thread() == QThread::currentThread(); return QCoreApplication::instance()->thread() == QThread::currentThread();
} }
static void assertInGuiThread() static void assertInGuiThread()
{ {
#ifdef _DEBUG #ifdef _DEBUG
assert(isGuiThread()); assert(isGuiThread());
#endif #endif
} }
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE

View file

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

View file

@ -1,31 +1,31 @@
#include <QApplication> #include <QApplication>
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
#include <QStandardPaths> #include <QStandardPaths>
#include "ABSettings.hpp" #include "ABSettings.hpp"
#include "ABTheme.hpp" #include "ABTheme.hpp"
#include "singletons/Fonts.hpp" #include "singletons/Fonts.hpp"
#include "widgets/BaseWindow.hpp" #include "widgets/BaseWindow.hpp"
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
using namespace AB_NAMESPACE; using namespace AB_NAMESPACE;
QApplication a(argc, argv); QApplication a(argc, argv);
auto path = auto path =
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
qDebug() << path; qDebug() << path;
QDir(path).mkdir("."); QDir(path).mkdir(".");
new Settings(path); new Settings(path);
new Fonts(); new Fonts();
BaseWindow widget(nullptr, BaseWindow::EnableCustomFrame); BaseWindow widget(nullptr, BaseWindow::EnableCustomFrame);
widget.setWindowTitle("asdf"); widget.setWindowTitle("asdf");
widget.show(); widget.show();
return a.exec(); return a.exec();
} }

View file

@ -1,100 +1,100 @@
#------------------------------------------------- #-------------------------------------------------
# #
# Project created by QtCreator 2018-11-19T19:03:22 # Project created by QtCreator 2018-11-19T19:03:22
# #
#------------------------------------------------- #-------------------------------------------------
!AB_NOT_STANDALONE { !AB_NOT_STANDALONE {
message(appbase standalone) message(appbase standalone)
QT += core gui widgets QT += core gui widgets
TARGET = main TARGET = main
TEMPLATE = app TEMPLATE = app
SOURCES += main.cpp SOURCES += main.cpp
# https://bugreports.qt.io/browse/QTBUG-27018 # https://bugreports.qt.io/browse/QTBUG-27018
equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") { equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
TARGET = bin/appbase TARGET = bin/appbase
} }
} }
#DEFINES += QT_DEPRECATED_WARNINGS #DEFINES += QT_DEPRECATED_WARNINGS
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000
macx { macx {
# osx (Tested on macOS Mojave and High Sierra) # osx (Tested on macOS Mojave and High Sierra)
CONFIG += c++17 CONFIG += c++17
} else { } else {
CONFIG += c++17 CONFIG += c++17
win32-msvc* { win32-msvc* {
# win32 msvc # win32 msvc
QMAKE_CXXFLAGS += /std:c++17 QMAKE_CXXFLAGS += /std:c++17
} else { } else {
# clang/gcc on linux or win32 # clang/gcc on linux or win32
QMAKE_CXXFLAGS += -std=c++17 QMAKE_CXXFLAGS += -std=c++17
} }
} }
debug { debug {
DEFINES += QT_DEBUG DEFINES += QT_DEBUG
} }
linux { linux {
LIBS += -lrt LIBS += -lrt
QMAKE_LFLAGS += -lrt QMAKE_LFLAGS += -lrt
} }
macx { macx {
INCLUDEPATH += /usr/local/include INCLUDEPATH += /usr/local/include
INCLUDEPATH += /usr/local/opt/openssl/include INCLUDEPATH += /usr/local/opt/openssl/include
LIBS += -L/usr/local/opt/openssl/lib LIBS += -L/usr/local/opt/openssl/lib
} }
SOURCES += \ SOURCES += \
$$PWD/BaseSettings.cpp \ $$PWD/BaseSettings.cpp \
$$PWD/BaseTheme.cpp \ $$PWD/BaseTheme.cpp \
$$PWD/common/ChatterinoSetting.cpp \ $$PWD/common/ChatterinoSetting.cpp \
$$PWD/debug/Benchmark.cpp \ $$PWD/debug/Benchmark.cpp \
$$PWD/singletons/Fonts.cpp \ $$PWD/singletons/Fonts.cpp \
$$PWD/util/FunctionEventFilter.cpp \ $$PWD/util/FunctionEventFilter.cpp \
$$PWD/util/FuzzyConvert.cpp \ $$PWD/util/FuzzyConvert.cpp \
$$PWD/util/Helpers.cpp \ $$PWD/util/Helpers.cpp \
$$PWD/util/WindowsHelper.cpp \ $$PWD/util/WindowsHelper.cpp \
$$PWD/widgets/BaseWidget.cpp \ $$PWD/widgets/BaseWidget.cpp \
$$PWD/widgets/BaseWindow.cpp \ $$PWD/widgets/BaseWindow.cpp \
$$PWD/widgets/Label.cpp \ $$PWD/widgets/Label.cpp \
$$PWD/widgets/TooltipWidget.cpp \ $$PWD/widgets/TooltipWidget.cpp \
$$PWD/widgets/helper/Button.cpp \ $$PWD/widgets/helper/Button.cpp \
$$PWD/widgets/helper/EffectLabel.cpp \ $$PWD/widgets/helper/EffectLabel.cpp \
$$PWD/widgets/helper/SignalLabel.cpp \ $$PWD/widgets/helper/SignalLabel.cpp \
$$PWD/widgets/helper/TitlebarButton.cpp \ $$PWD/widgets/helper/TitlebarButton.cpp \
HEADERS += \ HEADERS += \
$$PWD/BaseSettings.hpp \ $$PWD/BaseSettings.hpp \
$$PWD/BaseTheme.hpp \ $$PWD/BaseTheme.hpp \
$$PWD/common/ChatterinoSetting.hpp \ $$PWD/common/ChatterinoSetting.hpp \
$$PWD/common/FlagsEnum.hpp \ $$PWD/common/FlagsEnum.hpp \
$$PWD/common/Outcome.hpp \ $$PWD/common/Outcome.hpp \
$$PWD/common/Singleton.hpp \ $$PWD/common/Singleton.hpp \
$$PWD/debug/AssertInGuiThread.hpp \ $$PWD/debug/AssertInGuiThread.hpp \
$$PWD/debug/Benchmark.hpp \ $$PWD/debug/Benchmark.hpp \
$$PWD/debug/Log.hpp \ $$PWD/debug/Log.hpp \
$$PWD/singletons/Fonts.hpp \ $$PWD/singletons/Fonts.hpp \
$$PWD/util/Clamp.hpp \ $$PWD/util/Clamp.hpp \
$$PWD/util/CombinePath.hpp \ $$PWD/util/CombinePath.hpp \
$$PWD/util/DistanceBetweenPoints.hpp \ $$PWD/util/DistanceBetweenPoints.hpp \
$$PWD/util/FunctionEventFilter.hpp \ $$PWD/util/FunctionEventFilter.hpp \
$$PWD/util/FuzzyConvert.hpp \ $$PWD/util/FuzzyConvert.hpp \
$$PWD/util/Helpers.hpp \ $$PWD/util/Helpers.hpp \
$$PWD/util/LayoutHelper.hpp \ $$PWD/util/LayoutHelper.hpp \
$$PWD/util/PostToThread.hpp \ $$PWD/util/PostToThread.hpp \
$$PWD/util/RapidJsonSerializeQString.hpp \ $$PWD/util/RapidJsonSerializeQString.hpp \
$$PWD/util/Shortcut.hpp \ $$PWD/util/Shortcut.hpp \
$$PWD/util/WindowsHelper.hpp \ $$PWD/util/WindowsHelper.hpp \
$$PWD/widgets/BaseWidget.hpp \ $$PWD/widgets/BaseWidget.hpp \
$$PWD/widgets/BaseWindow.hpp \ $$PWD/widgets/BaseWindow.hpp \
$$PWD/widgets/Label.hpp \ $$PWD/widgets/Label.hpp \
$$PWD/widgets/TooltipWidget.hpp \ $$PWD/widgets/TooltipWidget.hpp \
$$PWD/widgets/helper/Button.hpp \ $$PWD/widgets/helper/Button.hpp \
$$PWD/widgets/helper/EffectLabel.hpp \ $$PWD/widgets/helper/EffectLabel.hpp \
$$PWD/widgets/helper/SignalLabel.hpp \ $$PWD/widgets/helper/SignalLabel.hpp \
$$PWD/widgets/helper/TitlebarButton.hpp \ $$PWD/widgets/helper/TitlebarButton.hpp \

View file

@ -1,13 +1,13 @@
#pragma once #pragma once
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
// http://en.cppreference.com/w/cpp/algorithm/clamp // http://en.cppreference.com/w/cpp/algorithm/clamp
template <class T> template <class T>
constexpr const T &clamp(const T &v, const T &lo, const T &hi) constexpr const T &clamp(const T &v, const T &lo, const T &hi)
{ {
return assert(!(hi < lo)), (v < lo) ? lo : (hi < v) ? hi : v; return assert(!(hi < lo)), (v < lo) ? lo : (hi < v) ? hi : v;
} }
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE

View file

@ -1,14 +1,14 @@
#pragma once #pragma once
#include <QDir> #include <QDir>
#include <QString> #include <QString>
namespace chatterino { namespace chatterino {
// https://stackoverflow.com/a/13014491 // https://stackoverflow.com/a/13014491
inline QString combinePath(const QString &a, const QString &b) inline QString combinePath(const QString &a, const QString &b)
{ {
return QDir::cleanPath(a + QDir::separator() + b); return QDir::cleanPath(a + QDir::separator() + b);
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,17 +1,17 @@
#include "FunctionEventFilter.hpp" #include "FunctionEventFilter.hpp"
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
FunctionEventFilter::FunctionEventFilter( FunctionEventFilter::FunctionEventFilter(
QObject *parent, std::function<bool(QObject *, QEvent *)> function) QObject *parent, std::function<bool(QObject *, QEvent *)> function)
: QObject(parent) : QObject(parent)
, function_(std::move(function)) , function_(std::move(function))
{ {
} }
bool FunctionEventFilter::eventFilter(QObject *watched, QEvent *event) bool FunctionEventFilter::eventFilter(QObject *watched, QEvent *event)
{ {
return this->function_(watched, event); return this->function_(watched, event);
} }
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE

View file

@ -1,24 +1,24 @@
#pragma once #pragma once
#include <QEvent> #include <QEvent>
#include <QObject> #include <QObject>
#include <functional> #include <functional>
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
class FunctionEventFilter : public QObject class FunctionEventFilter : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
FunctionEventFilter(QObject *parent, FunctionEventFilter(QObject *parent,
std::function<bool(QObject *, QEvent *)> function); std::function<bool(QObject *, QEvent *)> function);
protected: protected:
bool eventFilter(QObject *watched, QEvent *event) override; bool eventFilter(QObject *watched, QEvent *event) override;
private: private:
std::function<bool(QObject *, QEvent *)> function_; std::function<bool(QObject *, QEvent *)> function_;
}; };
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE

View file

@ -1,33 +1,33 @@
#include "FuzzyConvert.hpp" #include "FuzzyConvert.hpp"
#include <QRegularExpression> #include <QRegularExpression>
namespace chatterino { namespace chatterino {
int fuzzyToInt(const QString &str, int default_) int fuzzyToInt(const QString &str, int default_)
{ {
static auto intFinder = QRegularExpression("[0-9]+"); static auto intFinder = QRegularExpression("[0-9]+");
auto match = intFinder.match(str); auto match = intFinder.match(str);
if (match.hasMatch()) if (match.hasMatch())
{ {
return match.captured().toInt(); return match.captured().toInt();
} }
return default_; return default_;
} }
float fuzzyToFloat(const QString &str, float default_) float fuzzyToFloat(const QString &str, float default_)
{ {
static auto floatFinder = QRegularExpression("[0-9]+(\\.[0-9]+)?"); static auto floatFinder = QRegularExpression("[0-9]+(\\.[0-9]+)?");
auto match = floatFinder.match(str); auto match = floatFinder.match(str);
if (match.hasMatch()) if (match.hasMatch())
{ {
return match.captured().toFloat(); return match.captured().toFloat();
} }
return default_; return default_;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,10 +1,10 @@
#pragma once #pragma once
#include <QString> #include <QString>
namespace chatterino { namespace chatterino {
int fuzzyToInt(const QString &str, int default_); int fuzzyToInt(const QString &str, int default_);
float fuzzyToFloat(const QString &str, float default_); float fuzzyToFloat(const QString &str, float default_);
} // namespace chatterino } // namespace chatterino

View file

@ -1,42 +1,42 @@
#pragma once #pragma once
#include <QLayout> #include <QLayout>
#include <QWidget> #include <QWidget>
#include <boost/variant.hpp> #include <boost/variant.hpp>
namespace chatterino { namespace chatterino {
using LayoutItem = boost::variant<QWidget *, QLayout *>; using LayoutItem = boost::variant<QWidget *, QLayout *>;
template <typename T> template <typename T>
T *makeLayout(std::initializer_list<LayoutItem> items) T *makeLayout(std::initializer_list<LayoutItem> items)
{ {
auto t = new T; auto t = new T;
for (auto &item : items) for (auto &item : items)
{ {
switch (item.which()) switch (item.which())
{ {
case 0: case 0:
t->addItem(new QWidgetItem(boost::get<QWidget *>(item))); t->addItem(new QWidgetItem(boost::get<QWidget *>(item)));
break; break;
case 1: case 1:
t->addItem(boost::get<QLayout *>(item)); t->addItem(boost::get<QLayout *>(item));
break; break;
} }
} }
return t; return t;
} }
template <typename T, typename With> template <typename T, typename With>
T *makeWidget(With with) T *makeWidget(With with)
{ {
auto t = new T; auto t = new T;
with(t); with(t);
return t; return t;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,86 +1,86 @@
#include "WindowsHelper.hpp" #include "WindowsHelper.hpp"
#include <QSettings> #include <QSettings>
#ifdef USEWINSDK #ifdef USEWINSDK
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
typedef enum MONITOR_DPI_TYPE { typedef enum MONITOR_DPI_TYPE {
MDT_EFFECTIVE_DPI = 0, MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1, MDT_ANGULAR_DPI = 1,
MDT_RAW_DPI = 2, MDT_RAW_DPI = 2,
MDT_DEFAULT = MDT_EFFECTIVE_DPI MDT_DEFAULT = MDT_EFFECTIVE_DPI
} MONITOR_DPI_TYPE; } MONITOR_DPI_TYPE;
typedef HRESULT(CALLBACK *GetDpiForMonitor_)(HMONITOR, MONITOR_DPI_TYPE, UINT *, typedef HRESULT(CALLBACK *GetDpiForMonitor_)(HMONITOR, MONITOR_DPI_TYPE, UINT *,
UINT *); UINT *);
boost::optional<UINT> getWindowDpi(HWND hwnd) boost::optional<UINT> getWindowDpi(HWND hwnd)
{ {
static HINSTANCE shcore = LoadLibrary(L"Shcore.dll"); static HINSTANCE shcore = LoadLibrary(L"Shcore.dll");
if (shcore != nullptr) if (shcore != nullptr)
{ {
if (auto getDpiForMonitor = if (auto getDpiForMonitor =
GetDpiForMonitor_(GetProcAddress(shcore, "GetDpiForMonitor"))) GetDpiForMonitor_(GetProcAddress(shcore, "GetDpiForMonitor")))
{ {
HMONITOR monitor = HMONITOR monitor =
MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
UINT xScale, yScale; UINT xScale, yScale;
getDpiForMonitor(monitor, MDT_DEFAULT, &xScale, &yScale); getDpiForMonitor(monitor, MDT_DEFAULT, &xScale, &yScale);
return xScale; return xScale;
} }
} }
return boost::none; return boost::none;
} }
typedef HRESULT(CALLBACK *OleFlushClipboard_)(); typedef HRESULT(CALLBACK *OleFlushClipboard_)();
void flushClipboard() void flushClipboard()
{ {
static HINSTANCE ole32 = LoadLibrary(L"Ole32.dll"); static HINSTANCE ole32 = LoadLibrary(L"Ole32.dll");
if (ole32 != nullptr) if (ole32 != nullptr)
{ {
if (auto oleFlushClipboard = if (auto oleFlushClipboard =
OleFlushClipboard_(GetProcAddress(ole32, "OleFlushClipboard"))) OleFlushClipboard_(GetProcAddress(ole32, "OleFlushClipboard")))
{ {
oleFlushClipboard(); oleFlushClipboard();
} }
} }
} }
constexpr const char *runKey = constexpr const char *runKey =
"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"; "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
bool isRegisteredForStartup() bool isRegisteredForStartup()
{ {
QSettings settings(runKey, QSettings::NativeFormat); QSettings settings(runKey, QSettings::NativeFormat);
return !settings.value("Chatterino").toString().isEmpty(); return !settings.value("Chatterino").toString().isEmpty();
} }
void setRegisteredForStartup(bool isRegistered) void setRegisteredForStartup(bool isRegistered)
{ {
QSettings settings(runKey, QSettings::NativeFormat); QSettings settings(runKey, QSettings::NativeFormat);
if (isRegistered) if (isRegistered)
{ {
auto exePath = QFileInfo(QCoreApplication::applicationFilePath()) auto exePath = QFileInfo(QCoreApplication::applicationFilePath())
.absoluteFilePath() .absoluteFilePath()
.replace('/', '\\'); .replace('/', '\\');
settings.setValue("Chatterino", "\"" + exePath + "\" --autorun"); settings.setValue("Chatterino", "\"" + exePath + "\" --autorun");
} }
else else
{ {
settings.remove("Chatterino"); settings.remove("Chatterino");
} }
} }
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE
#endif #endif

View file

@ -1,18 +1,18 @@
#pragma once #pragma once
#ifdef USEWINSDK #ifdef USEWINSDK
# include <Windows.h> # include <Windows.h>
# include <boost/optional.hpp> # include <boost/optional.hpp>
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
boost::optional<UINT> getWindowDpi(HWND hwnd); boost::optional<UINT> getWindowDpi(HWND hwnd);
void flushClipboard(); void flushClipboard();
bool isRegisteredForStartup(); bool isRegisteredForStartup();
void setRegisteredForStartup(bool isRegistered); void setRegisteredForStartup(bool isRegistered);
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE
#endif #endif

File diff suppressed because it is too large Load diff

View file

@ -1,138 +1,138 @@
#pragma once #pragma once
#include "widgets/BaseWidget.hpp" #include "widgets/BaseWidget.hpp"
#include <functional> #include <functional>
#include <pajlada/signals/signalholder.hpp> #include <pajlada/signals/signalholder.hpp>
#include "common/FlagsEnum.hpp" #include "common/FlagsEnum.hpp"
class QHBoxLayout; class QHBoxLayout;
struct tagMSG; struct tagMSG;
typedef struct tagMSG MSG; typedef struct tagMSG MSG;
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
class Button; class Button;
class EffectLabel; class EffectLabel;
class TitleBarButton; class TitleBarButton;
enum class TitleBarButtonStyle; enum class TitleBarButtonStyle;
class BaseWindow : public BaseWidget class BaseWindow : public BaseWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
enum Flags { enum Flags {
None = 0, None = 0,
EnableCustomFrame = 1, EnableCustomFrame = 1,
Frameless = 2, Frameless = 2,
TopMost = 4, TopMost = 4,
DisableCustomScaling = 8, DisableCustomScaling = 8,
FramelessDraggable = 16, FramelessDraggable = 16,
}; };
enum ActionOnFocusLoss { Nothing, Delete, Close, Hide }; enum ActionOnFocusLoss { Nothing, Delete, Close, Hide };
explicit BaseWindow(FlagsEnum<Flags> flags_ = None, explicit BaseWindow(FlagsEnum<Flags> flags_ = None,
QWidget *parent = nullptr); QWidget *parent = nullptr);
void setInitialBounds(const QRect &bounds); void setInitialBounds(const QRect &bounds);
QRect getBounds(); QRect getBounds();
QWidget *getLayoutContainer(); QWidget *getLayoutContainer();
bool hasCustomWindowFrame(); bool hasCustomWindowFrame();
TitleBarButton *addTitleBarButton(const TitleBarButtonStyle &style, TitleBarButton *addTitleBarButton(const TitleBarButtonStyle &style,
std::function<void()> onClicked); std::function<void()> onClicked);
EffectLabel *addTitleBarLabel(std::function<void()> onClicked); EffectLabel *addTitleBarLabel(std::function<void()> onClicked);
void setStayInScreenRect(bool value); void setStayInScreenRect(bool value);
bool getStayInScreenRect() const; bool getStayInScreenRect() const;
void setActionOnFocusLoss(ActionOnFocusLoss value); void setActionOnFocusLoss(ActionOnFocusLoss value);
ActionOnFocusLoss getActionOnFocusLoss() const; ActionOnFocusLoss getActionOnFocusLoss() const;
void moveTo(QWidget *widget, QPoint point, bool offset = true); void moveTo(QWidget *widget, QPoint point, bool offset = true);
virtual float scale() const override; virtual float scale() const override;
float qtFontScale() const; float qtFontScale() const;
pajlada::Signals::NoArgSignal closing; pajlada::Signals::NoArgSignal closing;
static bool supportsCustomWindowFrame(); static bool supportsCustomWindowFrame();
protected: protected:
virtual bool nativeEvent(const QByteArray &eventType, void *message, virtual bool nativeEvent(const QByteArray &eventType, void *message,
long *result) override; long *result) override;
virtual void scaleChangedEvent(float) override; virtual void scaleChangedEvent(float) override;
virtual void paintEvent(QPaintEvent *) override; virtual void paintEvent(QPaintEvent *) override;
virtual void changeEvent(QEvent *) override; virtual void changeEvent(QEvent *) override;
virtual void leaveEvent(QEvent *) override; virtual void leaveEvent(QEvent *) override;
virtual void resizeEvent(QResizeEvent *) override; virtual void resizeEvent(QResizeEvent *) override;
virtual void moveEvent(QMoveEvent *) override; virtual void moveEvent(QMoveEvent *) override;
virtual void closeEvent(QCloseEvent *) override; virtual void closeEvent(QCloseEvent *) override;
virtual void showEvent(QShowEvent *) override; virtual void showEvent(QShowEvent *) override;
virtual void themeChangedEvent() override; virtual void themeChangedEvent() override;
virtual bool event(QEvent *event) override; virtual bool event(QEvent *event) override;
virtual void wheelEvent(QWheelEvent *event) override; virtual void wheelEvent(QWheelEvent *event) override;
void mousePressEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override;
QPointF movingRelativePos; QPointF movingRelativePos;
bool moving{}; bool moving{};
void updateScale(); void updateScale();
boost::optional<QColor> overrideBackgroundColor_; boost::optional<QColor> overrideBackgroundColor_;
private: private:
void init(); void init();
void moveIntoDesktopRect(QWidget *parent); void moveIntoDesktopRect(QWidget *parent);
void calcButtonsSizes(); void calcButtonsSizes();
void drawCustomWindowFrame(QPainter &painter); void drawCustomWindowFrame(QPainter &painter);
void onFocusLost(); void onFocusLost();
bool handleDPICHANGED(MSG *msg); bool handleDPICHANGED(MSG *msg);
bool handleSHOWWINDOW(MSG *msg); bool handleSHOWWINDOW(MSG *msg);
bool handleNCCALCSIZE(MSG *msg, long *result); bool handleNCCALCSIZE(MSG *msg, long *result);
bool handleSIZE(MSG *msg); bool handleSIZE(MSG *msg);
bool handleMOVE(MSG *msg); bool handleMOVE(MSG *msg);
bool handleNCHITTEST(MSG *msg, long *result); bool handleNCHITTEST(MSG *msg, long *result);
bool enableCustomFrame_; bool enableCustomFrame_;
ActionOnFocusLoss actionOnFocusLoss_ = Nothing; ActionOnFocusLoss actionOnFocusLoss_ = Nothing;
bool frameless_; bool frameless_;
bool stayInScreenRect_ = false; bool stayInScreenRect_ = false;
bool shown_ = false; bool shown_ = false;
FlagsEnum<Flags> flags_; FlagsEnum<Flags> flags_;
float nativeScale_ = 1; float nativeScale_ = 1;
struct { struct {
QLayout *windowLayout = nullptr; QLayout *windowLayout = nullptr;
QHBoxLayout *titlebarBox = nullptr; QHBoxLayout *titlebarBox = nullptr;
QWidget *titleLabel = nullptr; QWidget *titleLabel = nullptr;
TitleBarButton *minButton = nullptr; TitleBarButton *minButton = nullptr;
TitleBarButton *maxButton = nullptr; TitleBarButton *maxButton = nullptr;
TitleBarButton *exitButton = nullptr; TitleBarButton *exitButton = nullptr;
QWidget *layoutBase = nullptr; QWidget *layoutBase = nullptr;
std::vector<Button *> buttons; std::vector<Button *> buttons;
} ui_; } ui_;
#ifdef USEWINSDK #ifdef USEWINSDK
QRect initalBounds_; QRect initalBounds_;
QRect currentBounds_; QRect currentBounds_;
QRect nextBounds_; QRect nextBounds_;
QTimer useNextBounds_; QTimer useNextBounds_;
bool isNotMinimizedOrMaximized_{}; bool isNotMinimizedOrMaximized_{};
#endif #endif
pajlada::Signals::SignalHolder connections_; pajlada::Signals::SignalHolder connections_;
std::vector<pajlada::Signals::ScopedConnection> managedConnections_; std::vector<pajlada::Signals::ScopedConnection> managedConnections_;
friend class BaseWidget; friend class BaseWidget;
}; };
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE

View file

@ -1,132 +1,132 @@
#include "Label.hpp" #include "Label.hpp"
#include <QPainter> #include <QPainter>
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
Label::Label(QString text, FontStyle style) Label::Label(QString text, FontStyle style)
: Label(nullptr, text, style) : Label(nullptr, text, style)
{ {
} }
Label::Label(BaseWidget *parent, QString text, FontStyle style) Label::Label(BaseWidget *parent, QString text, FontStyle style)
: BaseWidget(parent) : BaseWidget(parent)
, text_(text) , text_(text)
, fontStyle_(style) , fontStyle_(style)
{ {
this->connections_.managedConnect(getFonts()->fontChanged, this->connections_.managedConnect(getFonts()->fontChanged,
[this] { this->updateSize(); }); [this] { this->updateSize(); });
} }
const QString &Label::getText() const const QString &Label::getText() const
{ {
return this->text_; return this->text_;
} }
void Label::setText(const QString &text) void Label::setText(const QString &text)
{ {
if (this->text_ != text) if (this->text_ != text)
{ {
this->text_ = text; this->text_ = text;
this->updateSize(); this->updateSize();
this->update(); this->update();
} }
} }
FontStyle Label::getFontStyle() const FontStyle Label::getFontStyle() const
{ {
return this->fontStyle_; return this->fontStyle_;
} }
bool Label::getCentered() const bool Label::getCentered() const
{ {
return this->centered_; return this->centered_;
} }
void Label::setCentered(bool centered) void Label::setCentered(bool centered)
{ {
this->centered_ = centered; this->centered_ = centered;
this->updateSize(); this->updateSize();
} }
bool Label::getHasOffset() const bool Label::getHasOffset() const
{ {
return this->hasOffset_; return this->hasOffset_;
} }
void Label::setHasOffset(bool hasOffset) void Label::setHasOffset(bool hasOffset)
{ {
this->hasOffset_ = hasOffset; this->hasOffset_ = hasOffset;
this->updateSize(); this->updateSize();
} }
void Label::setFontStyle(FontStyle style) void Label::setFontStyle(FontStyle style)
{ {
this->fontStyle_ = style; this->fontStyle_ = style;
this->updateSize(); this->updateSize();
} }
void Label::scaleChangedEvent(float scale) void Label::scaleChangedEvent(float scale)
{ {
this->updateSize(); this->updateSize();
} }
QSize Label::sizeHint() const QSize Label::sizeHint() const
{ {
return this->preferedSize_; return this->preferedSize_;
} }
QSize Label::minimumSizeHint() const QSize Label::minimumSizeHint() const
{ {
return this->preferedSize_; return this->preferedSize_;
} }
void Label::paintEvent(QPaintEvent *) void Label::paintEvent(QPaintEvent *)
{ {
QPainter painter(this); QPainter painter(this);
QFontMetrics metrics = getFonts()->getFontMetrics( QFontMetrics metrics = getFonts()->getFontMetrics(
this->getFontStyle(), this->getFontStyle(),
this->scale() * 96.f / this->logicalDpiX() * this->devicePixelRatioF()); this->scale() * 96.f / this->logicalDpiX() * this->devicePixelRatioF());
painter.setFont(getFonts()->getFont( painter.setFont(getFonts()->getFont(
this->getFontStyle(), this->scale() * 96.f / this->logicalDpiX() * this->getFontStyle(), this->scale() * 96.f / this->logicalDpiX() *
this->devicePixelRatioF())); this->devicePixelRatioF()));
int offset = this->getOffset(); int offset = this->getOffset();
// draw text // draw text
QRect textRect(offset, 0, this->width() - offset - offset, this->height()); QRect textRect(offset, 0, this->width() - offset - offset, this->height());
int width = metrics.width(this->text_); int width = metrics.width(this->text_);
Qt::Alignment alignment = !this->centered_ || width > textRect.width() Qt::Alignment alignment = !this->centered_ || width > textRect.width()
? Qt::AlignLeft | Qt::AlignVCenter ? Qt::AlignLeft | Qt::AlignVCenter
: Qt::AlignCenter; : Qt::AlignCenter;
painter.setBrush(this->palette().windowText()); painter.setBrush(this->palette().windowText());
QTextOption option(alignment); QTextOption option(alignment);
option.setWrapMode(QTextOption::NoWrap); option.setWrapMode(QTextOption::NoWrap);
painter.drawText(textRect, this->text_, option); painter.drawText(textRect, this->text_, option);
#if 0 #if 0
painter.setPen(QColor(255, 0, 0)); painter.setPen(QColor(255, 0, 0));
painter.drawRect(0, 0, this->width() - 1, this->height() - 1); painter.drawRect(0, 0, this->width() - 1, this->height() - 1);
#endif #endif
} }
void Label::updateSize() void Label::updateSize()
{ {
QFontMetrics metrics = QFontMetrics metrics =
getFonts()->getFontMetrics(this->fontStyle_, this->scale()); getFonts()->getFontMetrics(this->fontStyle_, this->scale());
int width = metrics.width(this->text_) + (2 * this->getOffset()); int width = metrics.width(this->text_) + (2 * this->getOffset());
int height = metrics.height(); int height = metrics.height();
this->preferedSize_ = QSize(width, height); this->preferedSize_ = QSize(width, height);
this->updateGeometry(); this->updateGeometry();
} }
int Label::getOffset() int Label::getOffset()
{ {
return this->hasOffset_ ? int(8 * this->scale()) : 0; return this->hasOffset_ ? int(8 * this->scale()) : 0;
} }
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE

View file

@ -1,50 +1,50 @@
#pragma once #pragma once
#include "singletons/Fonts.hpp" #include "singletons/Fonts.hpp"
#include "widgets/BaseWidget.hpp" #include "widgets/BaseWidget.hpp"
#include <pajlada/signals/signalholder.hpp> #include <pajlada/signals/signalholder.hpp>
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
class Label : public BaseWidget class Label : public BaseWidget
{ {
public: public:
explicit Label(QString text = QString(), explicit Label(QString text = QString(),
FontStyle style = FontStyle::UiMedium); FontStyle style = FontStyle::UiMedium);
explicit Label(BaseWidget *parent, QString text = QString(), explicit Label(BaseWidget *parent, QString text = QString(),
FontStyle style = FontStyle::UiMedium); FontStyle style = FontStyle::UiMedium);
const QString &getText() const; const QString &getText() const;
void setText(const QString &text); void setText(const QString &text);
FontStyle getFontStyle() const; FontStyle getFontStyle() const;
void setFontStyle(FontStyle style); void setFontStyle(FontStyle style);
bool getCentered() const; bool getCentered() const;
void setCentered(bool centered); void setCentered(bool centered);
bool getHasOffset() const; bool getHasOffset() const;
void setHasOffset(bool hasOffset); void setHasOffset(bool hasOffset);
protected: protected:
virtual void scaleChangedEvent(float scale_) override; virtual void scaleChangedEvent(float scale_) override;
virtual void paintEvent(QPaintEvent *) override; virtual void paintEvent(QPaintEvent *) override;
virtual QSize sizeHint() const override; virtual QSize sizeHint() const override;
virtual QSize minimumSizeHint() const override; virtual QSize minimumSizeHint() const override;
private: private:
void updateSize(); void updateSize();
int getOffset(); int getOffset();
QString text_; QString text_;
FontStyle fontStyle_; FontStyle fontStyle_;
QSize preferedSize_; QSize preferedSize_;
bool centered_ = false; bool centered_ = false;
bool hasOffset_ = true; bool hasOffset_ = true;
pajlada::Signals::SignalHolder connections_; pajlada::Signals::SignalHolder connections_;
}; };
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE

View file

@ -1,134 +1,134 @@
#include "TitlebarButton.hpp" #include "TitlebarButton.hpp"
#include "BaseTheme.hpp" #include "BaseTheme.hpp"
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
TitleBarButton::TitleBarButton() TitleBarButton::TitleBarButton()
: Button(nullptr) : Button(nullptr)
{ {
} }
TitleBarButtonStyle TitleBarButton::getButtonStyle() const TitleBarButtonStyle TitleBarButton::getButtonStyle() const
{ {
return this->style_; return this->style_;
} }
void TitleBarButton::setButtonStyle(TitleBarButtonStyle _style) void TitleBarButton::setButtonStyle(TitleBarButtonStyle _style)
{ {
this->style_ = _style; this->style_ = _style;
this->update(); this->update();
} }
void TitleBarButton::paintEvent(QPaintEvent *event) void TitleBarButton::paintEvent(QPaintEvent *event)
{ {
QPainter painter(this); QPainter painter(this);
painter.setOpacity(this->getCurrentDimAmount()); painter.setOpacity(this->getCurrentDimAmount());
QColor color = this->theme->window.text; QColor color = this->theme->window.text;
QColor background = this->theme->window.background; QColor background = this->theme->window.background;
int xD = this->height() / 3; int xD = this->height() / 3;
int centerX = this->width() / 2; int centerX = this->width() / 2;
painter.setRenderHint(QPainter::Antialiasing, false); painter.setRenderHint(QPainter::Antialiasing, false);
switch (this->style_) switch (this->style_)
{ {
case TitleBarButtonStyle::Minimize: case TitleBarButtonStyle::Minimize:
{ {
painter.fillRect(centerX - xD / 2, xD * 3 / 2, xD, 1, color); painter.fillRect(centerX - xD / 2, xD * 3 / 2, xD, 1, color);
break; break;
} }
case TitleBarButtonStyle::Maximize: case TitleBarButtonStyle::Maximize:
{ {
painter.setPen(color); painter.setPen(color);
painter.drawRect(centerX - xD / 2, xD, xD - 1, xD - 1); painter.drawRect(centerX - xD / 2, xD, xD - 1, xD - 1);
break; break;
} }
case TitleBarButtonStyle::Unmaximize: case TitleBarButtonStyle::Unmaximize:
{ {
int xD2 = xD * 1 / 5; int xD2 = xD * 1 / 5;
int xD3 = xD * 4 / 5; int xD3 = xD * 4 / 5;
painter.drawRect(centerX - xD / 2 + xD2, xD, xD3, xD3); painter.drawRect(centerX - xD / 2 + xD2, xD, xD3, xD3);
painter.fillRect(centerX - xD / 2, xD + xD2, xD3, xD3, painter.fillRect(centerX - xD / 2, xD + xD2, xD3, xD3,
this->theme->window.background); this->theme->window.background);
painter.drawRect(centerX - xD / 2, xD + xD2, xD3, xD3); painter.drawRect(centerX - xD / 2, xD + xD2, xD3, xD3);
break; break;
} }
case TitleBarButtonStyle::Close: case TitleBarButtonStyle::Close:
{ {
QRect rect(centerX - xD / 2, xD, xD - 1, xD - 1); QRect rect(centerX - xD / 2, xD, xD - 1, xD - 1);
painter.setPen(QPen(color, 1)); painter.setPen(QPen(color, 1));
painter.drawLine(rect.topLeft(), rect.bottomRight()); painter.drawLine(rect.topLeft(), rect.bottomRight());
painter.drawLine(rect.topRight(), rect.bottomLeft()); painter.drawLine(rect.topRight(), rect.bottomLeft());
break; break;
} }
case TitleBarButtonStyle::User: case TitleBarButtonStyle::User:
{ {
color = "#999"; color = "#999";
painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::HighQualityAntialiasing); painter.setRenderHint(QPainter::HighQualityAntialiasing);
auto a = xD / 3; auto a = xD / 3;
QPainterPath path; QPainterPath path;
painter.save(); painter.save();
painter.translate(3, 3); painter.translate(3, 3);
path.arcMoveTo(a, 4 * a, 6 * a, 6 * a, 0); path.arcMoveTo(a, 4 * a, 6 * a, 6 * a, 0);
path.arcTo(a, 4 * a, 6 * a, 6 * a, 0, 180); path.arcTo(a, 4 * a, 6 * a, 6 * a, 0, 180);
painter.fillPath(path, color); painter.fillPath(path, color);
painter.setBrush(background); painter.setBrush(background);
painter.drawEllipse(2 * a, 1 * a, 4 * a, 4 * a); painter.drawEllipse(2 * a, 1 * a, 4 * a, 4 * a);
painter.setBrush(color); painter.setBrush(color);
painter.drawEllipse(2.5 * a, 1.5 * a, 3 * a + 1, 3 * a); painter.drawEllipse(2.5 * a, 1.5 * a, 3 * a + 1, 3 * a);
painter.restore(); painter.restore();
break; break;
} }
case TitleBarButtonStyle::Settings: case TitleBarButtonStyle::Settings:
{ {
color = "#999"; color = "#999";
painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::HighQualityAntialiasing); painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.save(); painter.save();
painter.translate(3, 3); painter.translate(3, 3);
auto a = xD / 3; auto a = xD / 3;
QPainterPath path; QPainterPath path;
path.arcMoveTo(a, a, 6 * a, 6 * a, 0 - (360 / 32.0)); path.arcMoveTo(a, a, 6 * a, 6 * a, 0 - (360 / 32.0));
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
{ {
path.arcTo(a, a, 6 * a, 6 * a, i * (360 / 8.0) - (360 / 32.0), path.arcTo(a, a, 6 * a, 6 * a, i * (360 / 8.0) - (360 / 32.0),
(360 / 32.0)); (360 / 32.0));
path.arcTo(2 * a, 2 * a, 4 * a, 4 * a, path.arcTo(2 * a, 2 * a, 4 * a, 4 * a,
i * (360 / 8.0) + (360 / 32.0), (360 / 32.0)); i * (360 / 8.0) + (360 / 32.0), (360 / 32.0));
} }
painter.strokePath(path, color); painter.strokePath(path, color);
painter.fillPath(path, color); painter.fillPath(path, color);
painter.setBrush(background); painter.setBrush(background);
painter.drawEllipse(3 * a, 3 * a, 2 * a, 2 * a); painter.drawEllipse(3 * a, 3 * a, 2 * a, 2 * a);
painter.restore(); painter.restore();
break; break;
} }
default:; default:;
} }
Button::paintEvent(event); Button::paintEvent(event);
// this->fancyPaint(painter); // this->fancyPaint(painter);
} }
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE

View file

@ -1,32 +1,32 @@
#pragma once #pragma once
#include "widgets/helper/Button.hpp" #include "widgets/helper/Button.hpp"
namespace AB_NAMESPACE { namespace AB_NAMESPACE {
enum class TitleBarButtonStyle { enum class TitleBarButtonStyle {
None = 0, None = 0,
Minimize = 1, Minimize = 1,
Maximize = 2, Maximize = 2,
Unmaximize = 4, Unmaximize = 4,
Close = 8, Close = 8,
User = 16, User = 16,
Settings = 32 Settings = 32
}; };
class TitleBarButton : public Button class TitleBarButton : public Button
{ {
public: public:
TitleBarButton(); TitleBarButton();
TitleBarButtonStyle getButtonStyle() const; TitleBarButtonStyle getButtonStyle() const;
void setButtonStyle(TitleBarButtonStyle style_); void setButtonStyle(TitleBarButtonStyle style_);
protected: protected:
void paintEvent(QPaintEvent *) override; void paintEvent(QPaintEvent *) override;
private: private:
TitleBarButtonStyle style_; TitleBarButtonStyle style_;
}; };
} // namespace AB_NAMESPACE } // namespace AB_NAMESPACE

View file

@ -1,10 +1,10 @@
#pragma once #pragma once
namespace chatterino { namespace chatterino {
class Channel; class Channel;
class ChannelView; class ChannelView;
using ChannelPtr = std::shared_ptr<Channel>; using ChannelPtr = std::shared_ptr<Channel>;
struct Message; struct Message;
using MessagePtr = std::shared_ptr<const Message>; using MessagePtr = std::shared_ptr<const Message>;
} // namespace chatterino } // namespace chatterino

View file

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

View file

@ -1,47 +1,47 @@
#pragma once #pragma once
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <mutex> #include <mutex>
namespace chatterino { namespace chatterino {
template <typename T> template <typename T>
class Atomic : boost::noncopyable class Atomic : boost::noncopyable
{ {
public: public:
Atomic() Atomic()
{ {
} }
Atomic(T &&val) Atomic(T &&val)
: value_(val) : value_(val)
{ {
} }
T get() const T get() const
{ {
std::lock_guard<std::mutex> guard(this->mutex_); std::lock_guard<std::mutex> guard(this->mutex_);
return this->value_; return this->value_;
} }
void set(const T &val) void set(const T &val)
{ {
std::lock_guard<std::mutex> guard(this->mutex_); std::lock_guard<std::mutex> guard(this->mutex_);
this->value_ = val; this->value_ = val;
} }
void set(T &&val) void set(T &&val)
{ {
std::lock_guard<std::mutex> guard(this->mutex_); std::lock_guard<std::mutex> guard(this->mutex_);
this->value_ = std::move(val); this->value_ = std::move(val);
} }
private: private:
mutable std::mutex mutex_; mutable std::mutex mutex_;
T value_; T value_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,49 +1,49 @@
#pragma once #pragma once
#include "common/Aliases.hpp" #include "common/Aliases.hpp"
#include "common/Outcome.hpp" #include "common/Outcome.hpp"
#include "common/ProviderId.hpp" #include "common/ProviderId.hpp"
#include <QString> #include <QString>
#include <QWidget> #include <QWidget>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <boost/preprocessor.hpp> #include <boost/preprocessor.hpp>
#include <string> #include <string>
namespace chatterino { namespace chatterino {
enum class HighlightState { enum class HighlightState {
None, None,
Highlighted, Highlighted,
NewMessage, NewMessage,
}; };
inline QString qS(const std::string &string) inline QString qS(const std::string &string)
{ {
return QString::fromStdString(string); return QString::fromStdString(string);
} }
const Qt::KeyboardModifiers showSplitOverlayModifiers = const Qt::KeyboardModifiers showSplitOverlayModifiers =
Qt::ControlModifier | Qt::AltModifier; Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showAddSplitRegions = const Qt::KeyboardModifiers showAddSplitRegions =
Qt::ControlModifier | Qt::AltModifier; Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier; const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
static const char *ANONYMOUS_USERNAME_LABEL ATTR_UNUSED = " - anonymous - "; static const char *ANONYMOUS_USERNAME_LABEL ATTR_UNUSED = " - anonymous - ";
template <typename T> template <typename T>
std::weak_ptr<T> weakOf(T *element) std::weak_ptr<T> weakOf(T *element)
{ {
return element->shared_from_this(); return element->shared_from_this();
} }
struct Message; struct Message;
using MessagePtr = std::shared_ptr<const Message>; using MessagePtr = std::shared_ptr<const Message>;
enum class CopyMode { enum class CopyMode {
Everything, Everything,
OnlyTextAndEmotes, OnlyTextAndEmotes,
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,195 +1,195 @@
#include "common/CompletionModel.hpp" #include "common/CompletionModel.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "common/Common.hpp" #include "common/Common.hpp"
#include "common/UsernameSet.hpp" #include "common/UsernameSet.hpp"
#include "controllers/accounts/AccountController.hpp" #include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandController.hpp" #include "controllers/commands/CommandController.hpp"
#include "debug/Benchmark.hpp" #include "debug/Benchmark.hpp"
#include "debug/Log.hpp" #include "debug/Log.hpp"
#include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchServer.hpp" #include "providers/twitch/TwitchServer.hpp"
#include "singletons/Emotes.hpp" #include "singletons/Emotes.hpp"
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
#include <QtAlgorithms> #include <QtAlgorithms>
#include <utility> #include <utility>
namespace chatterino { namespace chatterino {
// //
// TaggedString // TaggedString
// //
CompletionModel::TaggedString::TaggedString(const QString &_string, Type _type) CompletionModel::TaggedString::TaggedString(const QString &_string, Type _type)
: string(_string) : string(_string)
, type(_type) , type(_type)
{ {
} }
bool CompletionModel::TaggedString::isEmote() const bool CompletionModel::TaggedString::isEmote() const
{ {
return this->type > Type::EmoteStart && this->type < Type::EmoteEnd; return this->type > Type::EmoteStart && this->type < Type::EmoteEnd;
} }
bool CompletionModel::TaggedString::operator<(const TaggedString &that) const bool CompletionModel::TaggedString::operator<(const TaggedString &that) const
{ {
if (this->isEmote() != that.isEmote()) if (this->isEmote() != that.isEmote())
{ {
return this->isEmote(); return this->isEmote();
} }
// try comparing insensitively, if they are the same then senstively // try comparing insensitively, if they are the same then senstively
// (fixes order of LuL and LUL) // (fixes order of LuL and LUL)
int k = QString::compare(this->string, that.string, Qt::CaseInsensitive); int k = QString::compare(this->string, that.string, Qt::CaseInsensitive);
if (k == 0) if (k == 0)
return this->string > that.string; return this->string > that.string;
return k < 0; return k < 0;
} }
// //
// CompletionModel // CompletionModel
// //
CompletionModel::CompletionModel(Channel &channel) CompletionModel::CompletionModel(Channel &channel)
: channel_(channel) : channel_(channel)
{ {
} }
int CompletionModel::columnCount(const QModelIndex &) const int CompletionModel::columnCount(const QModelIndex &) const
{ {
return 1; return 1;
} }
QVariant CompletionModel::data(const QModelIndex &index, int) const QVariant CompletionModel::data(const QModelIndex &index, int) const
{ {
std::lock_guard<std::mutex> lock(this->itemsMutex_); std::lock_guard<std::mutex> lock(this->itemsMutex_);
auto it = this->items_.begin(); auto it = this->items_.begin();
std::advance(it, index.row()); std::advance(it, index.row());
return QVariant(it->string); return QVariant(it->string);
} }
int CompletionModel::rowCount(const QModelIndex &) const int CompletionModel::rowCount(const QModelIndex &) const
{ {
std::lock_guard<std::mutex> lock(this->itemsMutex_); std::lock_guard<std::mutex> lock(this->itemsMutex_);
return this->items_.size(); return this->items_.size();
} }
void CompletionModel::refresh(const QString &prefix, bool isFirstWord) void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
{ {
std::function<void(const QString &, TaggedString::Type)> addString; std::function<void(const QString &, TaggedString::Type)> addString;
if (getSettings()->prefixOnlyEmoteCompletion) if (getSettings()->prefixOnlyEmoteCompletion)
{ {
addString = [&](const QString &str, TaggedString::Type type) { addString = [&](const QString &str, TaggedString::Type type) {
if (str.startsWith(prefix, Qt::CaseInsensitive)) if (str.startsWith(prefix, Qt::CaseInsensitive))
this->items_.emplace(str + " ", type); this->items_.emplace(str + " ", type);
}; };
} }
else else
{ {
addString = [&](const QString &str, TaggedString::Type type) { addString = [&](const QString &str, TaggedString::Type type) {
if (str.contains(prefix, Qt::CaseInsensitive)) if (str.contains(prefix, Qt::CaseInsensitive))
this->items_.emplace(str + " ", type); this->items_.emplace(str + " ", type);
}; };
} }
std::lock_guard<std::mutex> guard(this->itemsMutex_); std::lock_guard<std::mutex> guard(this->itemsMutex_);
this->items_.clear(); this->items_.clear();
if (prefix.length() < 2) if (prefix.length() < 2)
return; return;
if (auto channel = dynamic_cast<TwitchChannel *>(&this->channel_)) if (auto channel = dynamic_cast<TwitchChannel *>(&this->channel_))
{ {
// account emotes // account emotes
if (auto account = getApp()->accounts->twitch.getCurrent()) if (auto account = getApp()->accounts->twitch.getCurrent())
{ {
for (const auto &emote : account->accessEmotes()->allEmoteNames) for (const auto &emote : account->accessEmotes()->allEmoteNames)
{ {
// XXX: No way to discern between a twitch global emote and sub // XXX: No way to discern between a twitch global emote and sub
// emote right now // emote right now
addString(emote.string, TaggedString::Type::TwitchGlobalEmote); addString(emote.string, TaggedString::Type::TwitchGlobalEmote);
} }
} }
// Usernames // Usernames
if (prefix.length() >= UsernameSet::PrefixLength) if (prefix.length() >= UsernameSet::PrefixLength)
{ {
auto usernames = channel->accessChatters(); auto usernames = channel->accessChatters();
QString usernamePrefix = prefix; QString usernamePrefix = prefix;
QString usernamePostfix = QString usernamePostfix =
isFirstWord && getSettings()->mentionUsersWithComma ? "," isFirstWord && getSettings()->mentionUsersWithComma ? ","
: QString(); : QString();
if (usernamePrefix.startsWith("@")) if (usernamePrefix.startsWith("@"))
{ {
usernamePrefix.remove(0, 1); usernamePrefix.remove(0, 1);
for (const auto &name : for (const auto &name :
usernames->subrange(Prefix(usernamePrefix))) usernames->subrange(Prefix(usernamePrefix)))
{ {
addString("@" + name + usernamePostfix, addString("@" + name + usernamePostfix,
TaggedString::Type::Username); TaggedString::Type::Username);
} }
} }
else else
{ {
for (const auto &name : for (const auto &name :
usernames->subrange(Prefix(usernamePrefix))) usernames->subrange(Prefix(usernamePrefix)))
{ {
addString(name + usernamePostfix, addString(name + usernamePostfix,
TaggedString::Type::Username); TaggedString::Type::Username);
} }
} }
} }
// Bttv Global // Bttv Global
for (auto &emote : *channel->globalBttv().emotes()) for (auto &emote : *channel->globalBttv().emotes())
{ {
addString(emote.first.string, TaggedString::Type::BTTVChannelEmote); addString(emote.first.string, TaggedString::Type::BTTVChannelEmote);
} }
// Ffz Global // Ffz Global
for (auto &emote : *channel->globalFfz().emotes()) for (auto &emote : *channel->globalFfz().emotes())
{ {
addString(emote.first.string, TaggedString::Type::FFZChannelEmote); addString(emote.first.string, TaggedString::Type::FFZChannelEmote);
} }
// Bttv Channel // Bttv Channel
for (auto &emote : *channel->bttvEmotes()) for (auto &emote : *channel->bttvEmotes())
{ {
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote); addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
} }
// Ffz Channel // Ffz Channel
for (auto &emote : *channel->ffzEmotes()) for (auto &emote : *channel->ffzEmotes())
{ {
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote); addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
} }
// Emojis // Emojis
if (prefix.startsWith(":")) if (prefix.startsWith(":"))
{ {
const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes; const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes;
for (auto &m : emojiShortCodes) for (auto &m : emojiShortCodes)
{ {
addString(":" + m + ":", TaggedString::Type::Emoji); addString(":" + m + ":", TaggedString::Type::Emoji);
} }
} }
// Commands // Commands
for (auto &command : getApp()->commands->items_) for (auto &command : getApp()->commands->items_)
{ {
addString(command.name, TaggedString::Command); addString(command.name, TaggedString::Command);
} }
for (auto &command : getApp()->commands->getDefaultTwitchCommandList()) for (auto &command : getApp()->commands->getDefaultTwitchCommandList())
{ {
addString(command, TaggedString::Command); addString(command, TaggedString::Command);
} }
} }
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,60 +1,60 @@
#pragma once #pragma once
#include <QAbstractListModel> #include <QAbstractListModel>
#include <chrono> #include <chrono>
#include <mutex> #include <mutex>
#include <set> #include <set>
namespace chatterino { namespace chatterino {
class Channel; class Channel;
class CompletionModel : public QAbstractListModel class CompletionModel : public QAbstractListModel
{ {
struct TaggedString { struct TaggedString {
enum Type { enum Type {
Username, Username,
// emotes // emotes
EmoteStart, EmoteStart,
FFZGlobalEmote, FFZGlobalEmote,
FFZChannelEmote, FFZChannelEmote,
BTTVGlobalEmote, BTTVGlobalEmote,
BTTVChannelEmote, BTTVChannelEmote,
TwitchGlobalEmote, TwitchGlobalEmote,
TwitchSubscriberEmote, TwitchSubscriberEmote,
Emoji, Emoji,
EmoteEnd, EmoteEnd,
// end emotes // end emotes
Command, Command,
}; };
TaggedString(const QString &string, Type type); TaggedString(const QString &string, Type type);
bool isEmote() const; bool isEmote() const;
bool operator<(const TaggedString &that) const; bool operator<(const TaggedString &that) const;
QString string; QString string;
Type type; Type type;
}; };
public: public:
CompletionModel(Channel &channel); CompletionModel(Channel &channel);
virtual int columnCount(const QModelIndex &) const override; virtual int columnCount(const QModelIndex &) const override;
virtual QVariant data(const QModelIndex &index, int) const override; virtual QVariant data(const QModelIndex &index, int) const override;
virtual int rowCount(const QModelIndex &) const override; virtual int rowCount(const QModelIndex &) const override;
void refresh(const QString &prefix, bool isFirstWord = false); void refresh(const QString &prefix, bool isFirstWord = false);
private: private:
TaggedString createUser(const QString &str); TaggedString createUser(const QString &str);
std::set<TaggedString> items_; std::set<TaggedString> items_;
mutable std::mutex itemsMutex_; mutable std::mutex itemsMutex_;
Channel &channel_; Channel &channel_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,73 +1,73 @@
#pragma once #pragma once
#include <type_traits> #include <type_traits>
namespace chatterino { namespace chatterino {
template <typename T> template <typename T>
class NullablePtr class NullablePtr
{ {
public: public:
NullablePtr() NullablePtr()
: element_(nullptr) : element_(nullptr)
{ {
} }
NullablePtr(T *element) NullablePtr(T *element)
: element_(element) : element_(element)
{ {
} }
T *operator->() const T *operator->() const
{ {
assert(this->hasElement()); assert(this->hasElement());
return element_; return element_;
} }
typename std::add_lvalue_reference<T>::type operator*() const typename std::add_lvalue_reference<T>::type operator*() const
{ {
assert(this->hasElement()); assert(this->hasElement());
return *element_; return *element_;
} }
T *get() const T *get() const
{ {
assert(this->hasElement()); assert(this->hasElement());
return this->element_; return this->element_;
} }
bool isNull() const bool isNull() const
{ {
return this->element_ == nullptr; return this->element_ == nullptr;
} }
bool hasElement() const bool hasElement() const
{ {
return this->element_ != nullptr; return this->element_ != nullptr;
} }
operator bool() const operator bool() const
{ {
return this->hasElement(); return this->hasElement();
} }
bool operator!() const bool operator!() const
{ {
return !this->hasElement(); return !this->hasElement();
} }
template <typename X = T, template <typename X = T,
typename = std::enable_if_t<!std::is_const<X>::value>> typename = std::enable_if_t<!std::is_const<X>::value>>
operator NullablePtr<const T>() const operator NullablePtr<const T>() const
{ {
return NullablePtr<const T>(this->element_); return NullablePtr<const T>(this->element_);
} }
private: private:
T *element_; T *element_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,7 +1,7 @@
#pragma once #pragma once
namespace chatterino { namespace chatterino {
enum class ProviderId { Twitch, Irc }; enum class ProviderId { Twitch, Irc };
// //
} // namespace chatterino } // namespace chatterino

View file

@ -1,247 +1,247 @@
#pragma once #pragma once
#include <QStandardItemModel> #include <QStandardItemModel>
#include <QTimer> #include <QTimer>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <pajlada/signals/signal.hpp> #include <pajlada/signals/signal.hpp>
#include <shared_mutex> #include <shared_mutex>
#include <vector> #include <vector>
#include "debug/AssertInGuiThread.hpp" #include "debug/AssertInGuiThread.hpp"
namespace chatterino { namespace chatterino {
template <typename TVectorItem> template <typename TVectorItem>
struct SignalVectorItemArgs { struct SignalVectorItemArgs {
const TVectorItem &item; const TVectorItem &item;
int index; int index;
void *caller; void *caller;
}; };
template <typename TVectorItem> template <typename TVectorItem>
class ReadOnlySignalVector : boost::noncopyable class ReadOnlySignalVector : boost::noncopyable
{ {
using VecIt = typename std::vector<TVectorItem>::iterator; using VecIt = typename std::vector<TVectorItem>::iterator;
public: public:
struct Iterator struct Iterator
: public std::iterator<std::input_iterator_tag, TVectorItem> { : public std::iterator<std::input_iterator_tag, TVectorItem> {
Iterator(VecIt &&it, std::shared_mutex &mutex) Iterator(VecIt &&it, std::shared_mutex &mutex)
: it_(std::move(it)) : it_(std::move(it))
, lock_(mutex) , lock_(mutex)
, mutex_(mutex) , mutex_(mutex)
{ {
} }
Iterator(const Iterator &other) Iterator(const Iterator &other)
: it_(other.it_) : it_(other.it_)
, lock_(other.mutex_) , lock_(other.mutex_)
, mutex_(other.mutex_) , mutex_(other.mutex_)
{ {
} }
Iterator &operator=(const Iterator &other) Iterator &operator=(const Iterator &other)
{ {
this->lock_ = std::shared_lock(other.mutex_.get()); this->lock_ = std::shared_lock(other.mutex_.get());
this->mutex_ = other.mutex_; this->mutex_ = other.mutex_;
return *this; return *this;
} }
TVectorItem &operator*() TVectorItem &operator*()
{ {
return it_.operator*(); return it_.operator*();
} }
Iterator &operator++() Iterator &operator++()
{ {
++this->it_; ++this->it_;
return *this; return *this;
} }
bool operator==(const Iterator &other) bool operator==(const Iterator &other)
{ {
return this->it_ == other.it_; return this->it_ == other.it_;
} }
bool operator!=(const Iterator &other) bool operator!=(const Iterator &other)
{ {
return this->it_ != other.it_; return this->it_ != other.it_;
} }
auto operator-(const Iterator &other) auto operator-(const Iterator &other)
{ {
return this->it_ - other.it_; return this->it_ - other.it_;
} }
private: private:
VecIt it_; VecIt it_;
std::shared_lock<std::shared_mutex> lock_; std::shared_lock<std::shared_mutex> lock_;
std::reference_wrapper<std::shared_mutex> mutex_; std::reference_wrapper<std::shared_mutex> mutex_;
}; };
ReadOnlySignalVector() ReadOnlySignalVector()
{ {
QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout, QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout,
[this] { this->delayedItemsChanged.invoke(); }); [this] { this->delayedItemsChanged.invoke(); });
this->itemsChangedTimer_.setInterval(100); this->itemsChangedTimer_.setInterval(100);
this->itemsChangedTimer_.setSingleShot(true); this->itemsChangedTimer_.setSingleShot(true);
} }
virtual ~ReadOnlySignalVector() = default; virtual ~ReadOnlySignalVector() = default;
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemInserted; pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemInserted;
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemRemoved; pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemRemoved;
pajlada::Signals::NoArgSignal delayedItemsChanged; pajlada::Signals::NoArgSignal delayedItemsChanged;
Iterator begin() const Iterator begin() const
{ {
return Iterator( return Iterator(
const_cast<std::vector<TVectorItem> &>(this->vector_).begin(), const_cast<std::vector<TVectorItem> &>(this->vector_).begin(),
this->mutex_); this->mutex_);
} }
Iterator end() const Iterator end() const
{ {
return Iterator( return Iterator(
const_cast<std::vector<TVectorItem> &>(this->vector_).end(), const_cast<std::vector<TVectorItem> &>(this->vector_).end(),
this->mutex_); this->mutex_);
} }
bool empty() const bool empty() const
{ {
std::shared_lock lock(this->mutex_); std::shared_lock lock(this->mutex_);
return this->vector_.empty(); return this->vector_.empty();
} }
const std::vector<TVectorItem> &getVector() const const std::vector<TVectorItem> &getVector() const
{ {
assertInGuiThread(); assertInGuiThread();
return this->vector_; return this->vector_;
} }
std::vector<TVectorItem> cloneVector() const std::vector<TVectorItem> cloneVector() const
{ {
std::shared_lock lock(this->mutex_); std::shared_lock lock(this->mutex_);
return this->vector_; return this->vector_;
} }
void invokeDelayedItemsChanged() void invokeDelayedItemsChanged()
{ {
assertInGuiThread(); assertInGuiThread();
if (!this->itemsChangedTimer_.isActive()) if (!this->itemsChangedTimer_.isActive())
{ {
this->itemsChangedTimer_.start(); this->itemsChangedTimer_.start();
} }
} }
virtual bool isSorted() const = 0; virtual bool isSorted() const = 0;
protected: protected:
std::vector<TVectorItem> vector_; std::vector<TVectorItem> vector_;
QTimer itemsChangedTimer_; QTimer itemsChangedTimer_;
mutable std::shared_mutex mutex_; mutable std::shared_mutex mutex_;
}; };
template <typename TVectorItem> template <typename TVectorItem>
class BaseSignalVector : public ReadOnlySignalVector<TVectorItem> class BaseSignalVector : public ReadOnlySignalVector<TVectorItem>
{ {
public: public:
// returns the actual index of the inserted item // returns the actual index of the inserted item
virtual int insertItem(const TVectorItem &item, int proposedIndex = -1, virtual int insertItem(const TVectorItem &item, int proposedIndex = -1,
void *caller = nullptr) = 0; void *caller = nullptr) = 0;
void removeItem(int index, void *caller = nullptr) void removeItem(int index, void *caller = nullptr)
{ {
assertInGuiThread(); assertInGuiThread();
std::unique_lock lock(this->mutex_); std::unique_lock lock(this->mutex_);
assert(index >= 0 && index < int(this->vector_.size())); assert(index >= 0 && index < int(this->vector_.size()));
TVectorItem item = this->vector_[index]; TVectorItem item = this->vector_[index];
this->vector_.erase(this->vector_.begin() + index); this->vector_.erase(this->vector_.begin() + index);
lock.unlock(); // manual unlock lock.unlock(); // manual unlock
SignalVectorItemArgs<TVectorItem> args{item, index, caller}; SignalVectorItemArgs<TVectorItem> args{item, index, caller};
this->itemRemoved.invoke(args); this->itemRemoved.invoke(args);
this->invokeDelayedItemsChanged(); this->invokeDelayedItemsChanged();
} }
int appendItem(const TVectorItem &item, void *caller = nullptr) int appendItem(const TVectorItem &item, void *caller = nullptr)
{ {
return this->insertItem(item, -1, caller); return this->insertItem(item, -1, caller);
} }
}; };
template <typename TVectorItem> template <typename TVectorItem>
class UnsortedSignalVector : public BaseSignalVector<TVectorItem> class UnsortedSignalVector : public BaseSignalVector<TVectorItem>
{ {
public: public:
virtual int insertItem(const TVectorItem &item, int index = -1, virtual int insertItem(const TVectorItem &item, int index = -1,
void *caller = nullptr) override void *caller = nullptr) override
{ {
assertInGuiThread(); assertInGuiThread();
{ {
std::unique_lock lock(this->mutex_); std::unique_lock lock(this->mutex_);
if (index == -1) if (index == -1)
{ {
index = this->vector_.size(); index = this->vector_.size();
} }
else else
{ {
assert(index >= 0 && index <= this->vector_.size()); assert(index >= 0 && index <= this->vector_.size());
} }
this->vector_.insert(this->vector_.begin() + index, item); this->vector_.insert(this->vector_.begin() + index, item);
} }
SignalVectorItemArgs<TVectorItem> args{item, index, caller}; SignalVectorItemArgs<TVectorItem> args{item, index, caller};
this->itemInserted.invoke(args); this->itemInserted.invoke(args);
this->invokeDelayedItemsChanged(); this->invokeDelayedItemsChanged();
return index; return index;
} }
virtual bool isSorted() const override virtual bool isSorted() const override
{ {
return false; return false;
} }
}; };
template <typename TVectorItem, typename Compare> template <typename TVectorItem, typename Compare>
class SortedSignalVector : public BaseSignalVector<TVectorItem> class SortedSignalVector : public BaseSignalVector<TVectorItem>
{ {
public: public:
virtual int insertItem(const TVectorItem &item, int = -1, virtual int insertItem(const TVectorItem &item, int = -1,
void *caller = nullptr) override void *caller = nullptr) override
{ {
assertInGuiThread(); assertInGuiThread();
int index = -1; int index = -1;
{ {
std::unique_lock lock(this->mutex_); std::unique_lock lock(this->mutex_);
auto it = std::lower_bound(this->vector_.begin(), auto it = std::lower_bound(this->vector_.begin(),
this->vector_.end(), item, Compare{}); this->vector_.end(), item, Compare{});
index = it - this->vector_.begin(); index = it - this->vector_.begin();
this->vector_.insert(it, item); this->vector_.insert(it, item);
} }
SignalVectorItemArgs<TVectorItem> args{item, index, caller}; SignalVectorItemArgs<TVectorItem> args{item, index, caller};
this->itemInserted.invoke(args); this->itemInserted.invoke(args);
this->invokeDelayedItemsChanged(); this->invokeDelayedItemsChanged();
return index; return index;
} }
virtual bool isSorted() const override virtual bool isSorted() const override
{ {
return true; return true;
} }
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,357 +1,357 @@
#pragma once #pragma once
#include "common/SignalVector.hpp" #include "common/SignalVector.hpp"
#include <QAbstractTableModel> #include <QAbstractTableModel>
#include <QStandardItem> #include <QStandardItem>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <pajlada/signals/signalholder.hpp> #include <pajlada/signals/signalholder.hpp>
namespace chatterino { namespace chatterino {
template <typename TVectorItem> template <typename TVectorItem>
class SignalVectorModel : public QAbstractTableModel, class SignalVectorModel : public QAbstractTableModel,
pajlada::Signals::SignalHolder pajlada::Signals::SignalHolder
{ {
public: public:
SignalVectorModel(int columnCount, QObject *parent = nullptr) SignalVectorModel(int columnCount, QObject *parent = nullptr)
: QAbstractTableModel(parent) : QAbstractTableModel(parent)
, columnCount_(columnCount) , columnCount_(columnCount)
{ {
for (int i = 0; i < columnCount; i++) for (int i = 0; i < columnCount; i++)
{ {
this->headerData_.emplace_back(); this->headerData_.emplace_back();
} }
} }
void init(BaseSignalVector<TVectorItem> *vec) void init(BaseSignalVector<TVectorItem> *vec)
{ {
this->vector_ = vec; this->vector_ = vec;
auto insert = [this](const SignalVectorItemArgs<TVectorItem> &args) { auto insert = [this](const SignalVectorItemArgs<TVectorItem> &args) {
if (args.caller == this) if (args.caller == this)
{ {
return; return;
} }
// get row index // get row index
int index = this->getModelIndexFromVectorIndex(args.index); int index = this->getModelIndexFromVectorIndex(args.index);
assert(index >= 0 && index <= this->rows_.size()); assert(index >= 0 && index <= this->rows_.size());
// get row items // get row items
std::vector<QStandardItem *> row = this->createRow(); std::vector<QStandardItem *> row = this->createRow();
this->getRowFromItem(args.item, row); this->getRowFromItem(args.item, row);
// insert row // insert row
index = this->beforeInsert(args.item, row, index); index = this->beforeInsert(args.item, row, index);
this->beginInsertRows(QModelIndex(), index, index); this->beginInsertRows(QModelIndex(), index, index);
this->rows_.insert(this->rows_.begin() + index, this->rows_.insert(this->rows_.begin() + index,
Row(row, args.item)); Row(row, args.item));
this->endInsertRows(); this->endInsertRows();
}; };
int i = 0; int i = 0;
for (const TVectorItem &item : vec->getVector()) for (const TVectorItem &item : vec->getVector())
{ {
SignalVectorItemArgs<TVectorItem> args{item, i++, 0}; SignalVectorItemArgs<TVectorItem> args{item, i++, 0};
insert(args); insert(args);
} }
this->managedConnect(vec->itemInserted, insert); this->managedConnect(vec->itemInserted, insert);
this->managedConnect(vec->itemRemoved, [this](auto args) { this->managedConnect(vec->itemRemoved, [this](auto args) {
if (args.caller == this) if (args.caller == this)
{ {
return; return;
} }
int row = this->getModelIndexFromVectorIndex(args.index); int row = this->getModelIndexFromVectorIndex(args.index);
assert(row >= 0 && row <= this->rows_.size()); assert(row >= 0 && row <= this->rows_.size());
// remove row // remove row
std::vector<QStandardItem *> items = std::vector<QStandardItem *> items =
std::move(this->rows_[row].items); std::move(this->rows_[row].items);
this->beginRemoveRows(QModelIndex(), row, row); this->beginRemoveRows(QModelIndex(), row, row);
this->rows_.erase(this->rows_.begin() + row); this->rows_.erase(this->rows_.begin() + row);
this->endRemoveRows(); this->endRemoveRows();
this->afterRemoved(args.item, items, row); this->afterRemoved(args.item, items, row);
for (QStandardItem *item : items) for (QStandardItem *item : items)
{ {
delete item; delete item;
} }
}); });
this->afterInit(); this->afterInit();
} }
virtual ~SignalVectorModel() virtual ~SignalVectorModel()
{ {
for (Row &row : this->rows_) for (Row &row : this->rows_)
{ {
for (QStandardItem *item : row.items) for (QStandardItem *item : row.items)
{ {
delete item; delete item;
} }
} }
} }
int rowCount(const QModelIndex &parent) const override int rowCount(const QModelIndex &parent) const override
{ {
return this->rows_.size(); return this->rows_.size();
} }
int columnCount(const QModelIndex &parent) const override int columnCount(const QModelIndex &parent) const override
{ {
return this->columnCount_; return this->columnCount_;
} }
QVariant data(const QModelIndex &index, int role) const override QVariant data(const QModelIndex &index, int role) const override
{ {
int row = index.row(), column = index.column(); int row = index.row(), column = index.column();
assert(row >= 0 && row < this->rows_.size() && column >= 0 && assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
column < this->columnCount_); column < this->columnCount_);
return rows_[row].items[column]->data(role); return rows_[row].items[column]->data(role);
} }
bool setData(const QModelIndex &index, const QVariant &value, bool setData(const QModelIndex &index, const QVariant &value,
int role) override int role) override
{ {
int row = index.row(), column = index.column(); int row = index.row(), column = index.column();
assert(row >= 0 && row < this->rows_.size() && column >= 0 && assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
column < this->columnCount_); column < this->columnCount_);
Row &rowItem = this->rows_[row]; Row &rowItem = this->rows_[row];
rowItem.items[column]->setData(value, role); rowItem.items[column]->setData(value, role);
if (rowItem.isCustomRow) if (rowItem.isCustomRow)
{ {
this->customRowSetData(rowItem.items, column, value, role, row); this->customRowSetData(rowItem.items, column, value, role, row);
} }
else else
{ {
int vecRow = this->getVectorIndexFromModelIndex(row); int vecRow = this->getVectorIndexFromModelIndex(row);
this->vector_->removeItem(vecRow, this); this->vector_->removeItem(vecRow, this);
assert(this->rows_[row].original); assert(this->rows_[row].original);
TVectorItem item = this->getItemFromRow( TVectorItem item = this->getItemFromRow(
this->rows_[row].items, this->rows_[row].original.get()); this->rows_[row].items, this->rows_[row].original.get());
this->vector_->insertItem(item, vecRow, this); this->vector_->insertItem(item, vecRow, this);
} }
return true; return true;
} }
QVariant headerData(int section, Qt::Orientation orientation, QVariant headerData(int section, Qt::Orientation orientation,
int role) const override int role) const override
{ {
if (orientation != Qt::Horizontal) if (orientation != Qt::Horizontal)
{ {
return QVariant(); return QVariant();
} }
auto it = this->headerData_[section].find(role); auto it = this->headerData_[section].find(role);
if (it == this->headerData_[section].end()) if (it == this->headerData_[section].end())
{ {
return QVariant(); return QVariant();
} }
else else
{ {
return it.value(); return it.value();
} }
} }
bool setHeaderData(int section, Qt::Orientation orientation, bool setHeaderData(int section, Qt::Orientation orientation,
const QVariant &value, const QVariant &value,
int role = Qt::DisplayRole) override int role = Qt::DisplayRole) override
{ {
if (orientation != Qt::Horizontal) if (orientation != Qt::Horizontal)
{ {
return false; return false;
} }
this->headerData_[section][role] = value; this->headerData_[section][role] = value;
emit this->headerDataChanged(Qt::Horizontal, section, section); emit this->headerDataChanged(Qt::Horizontal, section, section);
return true; return true;
} }
Qt::ItemFlags flags(const QModelIndex &index) const override Qt::ItemFlags flags(const QModelIndex &index) const override
{ {
int row = index.row(), column = index.column(); int row = index.row(), column = index.column();
assert(row >= 0 && row < this->rows_.size() && column >= 0 && assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
column < this->columnCount_); column < this->columnCount_);
return this->rows_[row].items[column]->flags(); return this->rows_[row].items[column]->flags();
} }
QStandardItem *getItem(int row, int column) QStandardItem *getItem(int row, int column)
{ {
assert(row >= 0 && row < this->rows_.size() && column >= 0 && assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
column < this->columnCount_); column < this->columnCount_);
return rows_[row].items[column]; return rows_[row].items[column];
} }
void deleteRow(int row) void deleteRow(int row)
{ {
int signalVectorRow = this->getVectorIndexFromModelIndex(row); int signalVectorRow = this->getVectorIndexFromModelIndex(row);
this->vector_->removeItem(signalVectorRow); this->vector_->removeItem(signalVectorRow);
} }
bool removeRows(int row, int count, const QModelIndex &parent) override bool removeRows(int row, int count, const QModelIndex &parent) override
{ {
if (count != 1) if (count != 1)
{ {
return false; return false;
} }
assert(row >= 0 && row < this->rows_.size()); assert(row >= 0 && row < this->rows_.size());
int signalVectorRow = this->getVectorIndexFromModelIndex(row); int signalVectorRow = this->getVectorIndexFromModelIndex(row);
this->vector_->removeItem(signalVectorRow); this->vector_->removeItem(signalVectorRow);
return true; return true;
} }
protected: protected:
virtual void afterInit() virtual void afterInit()
{ {
} }
// turn a vector item into a model row // turn a vector item into a model row
virtual TVectorItem getItemFromRow(std::vector<QStandardItem *> &row, virtual TVectorItem getItemFromRow(std::vector<QStandardItem *> &row,
const TVectorItem &original) = 0; const TVectorItem &original) = 0;
// turns a row in the model into a vector item // turns a row in the model into a vector item
virtual void getRowFromItem(const TVectorItem &item, virtual void getRowFromItem(const TVectorItem &item,
std::vector<QStandardItem *> &row) = 0; std::vector<QStandardItem *> &row) = 0;
virtual int beforeInsert(const TVectorItem &item, virtual int beforeInsert(const TVectorItem &item,
std::vector<QStandardItem *> &row, std::vector<QStandardItem *> &row,
int proposedIndex) int proposedIndex)
{ {
return proposedIndex; return proposedIndex;
} }
virtual void afterRemoved(const TVectorItem &item, virtual void afterRemoved(const TVectorItem &item,
std::vector<QStandardItem *> &row, int index) std::vector<QStandardItem *> &row, int index)
{ {
} }
virtual void customRowSetData(const std::vector<QStandardItem *> &row, virtual void customRowSetData(const std::vector<QStandardItem *> &row,
int column, const QVariant &value, int role, int column, const QVariant &value, int role,
int rowIndex) int rowIndex)
{ {
} }
void insertCustomRow(std::vector<QStandardItem *> row, int index) void insertCustomRow(std::vector<QStandardItem *> row, int index)
{ {
assert(index >= 0 && index <= this->rows_.size()); assert(index >= 0 && index <= this->rows_.size());
this->beginInsertRows(QModelIndex(), index, index); this->beginInsertRows(QModelIndex(), index, index);
this->rows_.insert(this->rows_.begin() + index, this->rows_.insert(this->rows_.begin() + index,
Row(std::move(row), true)); Row(std::move(row), true));
this->endInsertRows(); this->endInsertRows();
} }
void removeCustomRow(int index) void removeCustomRow(int index)
{ {
assert(index >= 0 && index <= this->rows_.size()); assert(index >= 0 && index <= this->rows_.size());
assert(this->rows_[index].isCustomRow); assert(this->rows_[index].isCustomRow);
this->beginRemoveRows(QModelIndex(), index, index); this->beginRemoveRows(QModelIndex(), index, index);
this->rows_.erase(this->rows_.begin() + index); this->rows_.erase(this->rows_.begin() + index);
this->endRemoveRows(); this->endRemoveRows();
} }
std::vector<QStandardItem *> createRow() std::vector<QStandardItem *> createRow()
{ {
std::vector<QStandardItem *> row; std::vector<QStandardItem *> row;
for (int i = 0; i < this->columnCount_; i++) for (int i = 0; i < this->columnCount_; i++)
{ {
row.push_back(new QStandardItem()); row.push_back(new QStandardItem());
} }
return row; return row;
} }
struct Row { struct Row {
std::vector<QStandardItem *> items; std::vector<QStandardItem *> items;
boost::optional<TVectorItem> original; boost::optional<TVectorItem> original;
bool isCustomRow; bool isCustomRow;
Row(std::vector<QStandardItem *> _items, bool _isCustomRow = false) Row(std::vector<QStandardItem *> _items, bool _isCustomRow = false)
: items(std::move(_items)) : items(std::move(_items))
, isCustomRow(_isCustomRow) , isCustomRow(_isCustomRow)
{ {
} }
Row(std::vector<QStandardItem *> _items, const TVectorItem &_original, Row(std::vector<QStandardItem *> _items, const TVectorItem &_original,
bool _isCustomRow = false) bool _isCustomRow = false)
: items(std::move(_items)) : items(std::move(_items))
, original(_original) , original(_original)
, isCustomRow(_isCustomRow) , isCustomRow(_isCustomRow)
{ {
} }
}; };
private: private:
std::vector<QMap<int, QVariant>> headerData_; std::vector<QMap<int, QVariant>> headerData_;
BaseSignalVector<TVectorItem> *vector_; BaseSignalVector<TVectorItem> *vector_;
std::vector<Row> rows_; std::vector<Row> rows_;
int columnCount_; int columnCount_;
// returns the related index of the SignalVector // returns the related index of the SignalVector
int getVectorIndexFromModelIndex(int index) int getVectorIndexFromModelIndex(int index)
{ {
int i = 0; int i = 0;
for (auto &row : this->rows_) for (auto &row : this->rows_)
{ {
if (row.isCustomRow) if (row.isCustomRow)
{ {
index--; index--;
continue; continue;
} }
if (i == index) if (i == index)
{ {
return i; return i;
} }
i++; i++;
} }
return i; return i;
} }
// returns the related index of the model // returns the related index of the model
int getModelIndexFromVectorIndex(int index) int getModelIndexFromVectorIndex(int index)
{ {
int i = 0; int i = 0;
for (auto &row : this->rows_) for (auto &row : this->rows_)
{ {
if (row.isCustomRow) if (row.isCustomRow)
{ {
index++; index++;
} }
if (i == index) if (i == index)
{ {
return i; return i;
} }
i++; i++;
} }
return i; return i;
} }
}; };
} // namespace chatterino } // namespace chatterino

View file

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

View file

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

View file

@ -1,15 +1,15 @@
#pragma once #pragma once
#include <QtGlobal> #include <QtGlobal>
#define CHATTERINO_VERSION "2.1.4-beta-2" #define CHATTERINO_VERSION "2.1.4-beta-2"
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
# define CHATTERINO_OS "win" # define CHATTERINO_OS "win"
#elif defined(Q_OS_MACOS) #elif defined(Q_OS_MACOS)
# define CHATTERINO_OS "macos" # define CHATTERINO_OS "macos"
#elif defined(Q_OS_LINUX) #elif defined(Q_OS_LINUX)
# define CHATTERINO_OS "linux" # define CHATTERINO_OS "linux"
#else #else
# define CHATTERINO_OS "unknown" # define CHATTERINO_OS "unknown"
#endif #endif

View file

@ -1,40 +1,40 @@
#include "Account.hpp" #include "Account.hpp"
#include <tuple> #include <tuple>
namespace chatterino { namespace chatterino {
Account::Account(ProviderId providerId) Account::Account(ProviderId providerId)
: providerId_(providerId) : providerId_(providerId)
{ {
static QString twitch("Twitch"); static QString twitch("Twitch");
this->category_ = [&]() { this->category_ = [&]() {
switch (providerId) switch (providerId)
{ {
case ProviderId::Twitch: case ProviderId::Twitch:
return twitch; return twitch;
} }
return QString("Unknown ProviderId"); return QString("Unknown ProviderId");
}(); }();
} }
const QString &Account::getCategory() const const QString &Account::getCategory() const
{ {
return this->category_; return this->category_;
} }
ProviderId Account::getProviderId() const ProviderId Account::getProviderId() const
{ {
return this->providerId_; return this->providerId_;
} }
bool Account::operator<(const Account &other) const bool Account::operator<(const Account &other) const
{ {
QString a = this->toString(); QString a = this->toString();
QString b = other.toString(); QString b = other.toString();
return std::tie(this->category_, a) < std::tie(other.category_, b); return std::tie(this->category_, a) < std::tie(other.category_, b);
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,26 +1,26 @@
#pragma once #pragma once
#include "common/ProviderId.hpp" #include "common/ProviderId.hpp"
#include <QString> #include <QString>
namespace chatterino { namespace chatterino {
class Account class Account
{ {
public: public:
Account(ProviderId providerId); Account(ProviderId providerId);
virtual ~Account() = default; virtual ~Account() = default;
virtual QString toString() const = 0; virtual QString toString() const = 0;
const QString &getCategory() const; const QString &getCategory() const;
ProviderId getProviderId() const; ProviderId getProviderId() const;
bool operator<(const Account &other) const; bool operator<(const Account &other) const;
private: private:
ProviderId providerId_; ProviderId providerId_;
QString category_; QString category_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,58 +1,58 @@
#include "AccountController.hpp" #include "AccountController.hpp"
#include "controllers/accounts/Account.hpp" #include "controllers/accounts/Account.hpp"
#include "controllers/accounts/AccountModel.hpp" #include "controllers/accounts/AccountModel.hpp"
#include "providers/twitch/TwitchAccount.hpp" #include "providers/twitch/TwitchAccount.hpp"
namespace chatterino { namespace chatterino {
AccountController::AccountController() AccountController::AccountController()
{ {
this->twitch.accounts.itemInserted.connect([this](const auto &args) { this->twitch.accounts.itemInserted.connect([this](const auto &args) {
this->accounts_.insertItem( this->accounts_.insertItem(
std::dynamic_pointer_cast<Account>(args.item)); std::dynamic_pointer_cast<Account>(args.item));
}); });
this->twitch.accounts.itemRemoved.connect([this](const auto &args) { this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
if (args.caller != this) if (args.caller != this)
{ {
auto &accs = this->twitch.accounts.getVector(); auto &accs = this->twitch.accounts.getVector();
auto it = std::find(accs.begin(), accs.end(), args.item); auto it = std::find(accs.begin(), accs.end(), args.item);
assert(it != accs.end()); assert(it != accs.end());
this->accounts_.removeItem(it - accs.begin(), this); this->accounts_.removeItem(it - accs.begin(), this);
} }
}); });
this->accounts_.itemRemoved.connect([this](const auto &args) { this->accounts_.itemRemoved.connect([this](const auto &args) {
switch (args.item->getProviderId()) switch (args.item->getProviderId())
{ {
case ProviderId::Twitch: case ProviderId::Twitch:
{ {
if (args.caller != this) if (args.caller != this)
{ {
auto accs = this->twitch.accounts.cloneVector(); auto accs = this->twitch.accounts.cloneVector();
auto it = std::find(accs.begin(), accs.end(), args.item); auto it = std::find(accs.begin(), accs.end(), args.item);
assert(it != accs.end()); assert(it != accs.end());
this->twitch.accounts.removeItem(it - accs.begin(), this); this->twitch.accounts.removeItem(it - accs.begin(), this);
} }
} }
break; break;
} }
}); });
} }
void AccountController::initialize(Settings &settings, Paths &paths) void AccountController::initialize(Settings &settings, Paths &paths)
{ {
this->twitch.load(); this->twitch.load();
} }
AccountModel *AccountController::createModel(QObject *parent) AccountModel *AccountController::createModel(QObject *parent)
{ {
AccountModel *model = new AccountModel(parent); AccountModel *model = new AccountModel(parent);
model->init(&this->accounts_); model->init(&this->accounts_);
return model; return model;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,34 +1,34 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include "common/SignalVector.hpp" #include "common/SignalVector.hpp"
#include "common/Singleton.hpp" #include "common/Singleton.hpp"
#include "providers/twitch/TwitchAccountManager.hpp" #include "providers/twitch/TwitchAccountManager.hpp"
#include "util/SharedPtrElementLess.hpp" #include "util/SharedPtrElementLess.hpp"
namespace chatterino { namespace chatterino {
class Account; class Account;
class Settings; class Settings;
class Paths; class Paths;
class AccountModel; class AccountModel;
class AccountController final : public Singleton class AccountController final : public Singleton
{ {
public: public:
AccountController(); AccountController();
AccountModel *createModel(QObject *parent); AccountModel *createModel(QObject *parent);
virtual void initialize(Settings &settings, Paths &paths) override; virtual void initialize(Settings &settings, Paths &paths) override;
TwitchAccountManager twitch; TwitchAccountManager twitch;
private: private:
SortedSignalVector<std::shared_ptr<Account>, SharedPtrElementLess<Account>> SortedSignalVector<std::shared_ptr<Account>, SharedPtrElementLess<Account>>
accounts_; accounts_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,64 +1,64 @@
#include "AccountModel.hpp" #include "AccountModel.hpp"
#include "controllers/accounts/Account.hpp" #include "controllers/accounts/Account.hpp"
#include "util/StandardItemHelper.hpp" #include "util/StandardItemHelper.hpp"
namespace chatterino { namespace chatterino {
AccountModel::AccountModel(QObject *parent) AccountModel::AccountModel(QObject *parent)
: SignalVectorModel<std::shared_ptr<Account>>(1, parent) : SignalVectorModel<std::shared_ptr<Account>>(1, parent)
{ {
} }
// turn a vector item into a model row // turn a vector item into a model row
std::shared_ptr<Account> AccountModel::getItemFromRow( std::shared_ptr<Account> AccountModel::getItemFromRow(
std::vector<QStandardItem *> &, const std::shared_ptr<Account> &original) std::vector<QStandardItem *> &, const std::shared_ptr<Account> &original)
{ {
return original; return original;
} }
// turns a row in the model into a vector item // turns a row in the model into a vector item
void AccountModel::getRowFromItem(const std::shared_ptr<Account> &item, void AccountModel::getRowFromItem(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row) std::vector<QStandardItem *> &row)
{ {
setStringItem(row[0], item->toString(), false); setStringItem(row[0], item->toString(), false);
row[0]->setData(QFont("Segoe UI", 10), Qt::FontRole); row[0]->setData(QFont("Segoe UI", 10), Qt::FontRole);
} }
int AccountModel::beforeInsert(const std::shared_ptr<Account> &item, int AccountModel::beforeInsert(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, std::vector<QStandardItem *> &row,
int proposedIndex) int proposedIndex)
{ {
if (this->categoryCount_[item->getCategory()]++ == 0) if (this->categoryCount_[item->getCategory()]++ == 0)
{ {
auto newRow = this->createRow(); auto newRow = this->createRow();
setStringItem(newRow[0], item->getCategory(), false, false); setStringItem(newRow[0], item->getCategory(), false, false);
newRow[0]->setData(QFont("Segoe UI Light", 16), Qt::FontRole); newRow[0]->setData(QFont("Segoe UI Light", 16), Qt::FontRole);
this->insertCustomRow(std::move(newRow), proposedIndex); this->insertCustomRow(std::move(newRow), proposedIndex);
return proposedIndex + 1; return proposedIndex + 1;
} }
return proposedIndex; return proposedIndex;
} }
void AccountModel::afterRemoved(const std::shared_ptr<Account> &item, void AccountModel::afterRemoved(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, int index) std::vector<QStandardItem *> &row, int index)
{ {
auto it = this->categoryCount_.find(item->getCategory()); auto it = this->categoryCount_.find(item->getCategory());
assert(it != this->categoryCount_.end()); assert(it != this->categoryCount_.end());
if (it->second <= 1) if (it->second <= 1)
{ {
this->categoryCount_.erase(it); this->categoryCount_.erase(it);
this->removeCustomRow(index - 1); this->removeCustomRow(index - 1);
} }
else else
{ {
it->second--; it->second--;
} }
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,43 +1,43 @@
#pragma once #pragma once
#include "common/SignalVectorModel.hpp" #include "common/SignalVectorModel.hpp"
#include "controllers/accounts/Account.hpp" #include "controllers/accounts/Account.hpp"
#include "util/QStringHash.hpp" #include "util/QStringHash.hpp"
#include <unordered_map> #include <unordered_map>
namespace chatterino { namespace chatterino {
class Account; class Account;
class AccountController; class AccountController;
class AccountModel : public SignalVectorModel<std::shared_ptr<Account>> class AccountModel : public SignalVectorModel<std::shared_ptr<Account>>
{ {
public: public:
AccountModel(QObject *parent); AccountModel(QObject *parent);
protected: protected:
// turn a vector item into a model row // turn a vector item into a model row
virtual std::shared_ptr<Account> getItemFromRow( virtual std::shared_ptr<Account> getItemFromRow(
std::vector<QStandardItem *> &row, std::vector<QStandardItem *> &row,
const std::shared_ptr<Account> &original) override; const std::shared_ptr<Account> &original) override;
// turns a row in the model into a vector item // turns a row in the model into a vector item
virtual void getRowFromItem(const std::shared_ptr<Account> &item, virtual void getRowFromItem(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row) override; std::vector<QStandardItem *> &row) override;
virtual int beforeInsert(const std::shared_ptr<Account> &item, virtual int beforeInsert(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, std::vector<QStandardItem *> &row,
int proposedIndex) override; int proposedIndex) override;
virtual void afterRemoved(const std::shared_ptr<Account> &item, virtual void afterRemoved(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, std::vector<QStandardItem *> &row,
int index) override; int index) override;
friend class AccountController; friend class AccountController;
private: private:
std::unordered_map<QString, int> categoryCount_; std::unordered_map<QString, int> categoryCount_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,31 +1,31 @@
#include "Command.hpp" #include "Command.hpp"
namespace chatterino { namespace chatterino {
// command // command
Command::Command(const QString &_text) Command::Command(const QString &_text)
{ {
int index = _text.indexOf(' '); int index = _text.indexOf(' ');
if (index == -1) if (index == -1)
{ {
this->name = _text; this->name = _text;
return; return;
} }
this->name = _text.mid(0, index).trimmed(); this->name = _text.mid(0, index).trimmed();
this->func = _text.mid(index + 1).trimmed(); this->func = _text.mid(index + 1).trimmed();
} }
Command::Command(const QString &_name, const QString &_func) Command::Command(const QString &_name, const QString &_func)
: name(_name.trimmed()) : name(_name.trimmed())
, func(_func.trimmed()) , func(_func.trimmed())
{ {
} }
QString Command::toString() const QString Command::toString() const
{ {
return this->name + " " + this->func; return this->name + " " + this->func;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,67 +1,67 @@
#pragma once #pragma once
#include "util/RapidjsonHelpers.hpp" #include "util/RapidjsonHelpers.hpp"
#include <QString> #include <QString>
#include <pajlada/serialize.hpp> #include <pajlada/serialize.hpp>
namespace chatterino { namespace chatterino {
struct Command { struct Command {
QString name; QString name;
QString func; QString func;
Command() = default; Command() = default;
explicit Command(const QString &text); explicit Command(const QString &text);
Command(const QString &name, const QString &func); Command(const QString &name, const QString &func);
QString toString() const; QString toString() const;
}; };
} // namespace chatterino } // namespace chatterino
namespace pajlada { namespace pajlada {
template <> template <>
struct Serialize<chatterino::Command> { struct Serialize<chatterino::Command> {
static rapidjson::Value get(const chatterino::Command &value, static rapidjson::Value get(const chatterino::Command &value,
rapidjson::Document::AllocatorType &a) rapidjson::Document::AllocatorType &a)
{ {
rapidjson::Value ret(rapidjson::kObjectType); rapidjson::Value ret(rapidjson::kObjectType);
chatterino::rj::set(ret, "name", value.name, a); chatterino::rj::set(ret, "name", value.name, a);
chatterino::rj::set(ret, "func", value.func, a); chatterino::rj::set(ret, "func", value.func, a);
return ret; return ret;
} }
}; };
template <> template <>
struct Deserialize<chatterino::Command> { struct Deserialize<chatterino::Command> {
static chatterino::Command get(const rapidjson::Value &value, static chatterino::Command get(const rapidjson::Value &value,
bool *error = nullptr) bool *error = nullptr)
{ {
chatterino::Command command; chatterino::Command command;
if (!value.IsObject()) if (!value.IsObject())
{ {
PAJLADA_REPORT_ERROR(error); PAJLADA_REPORT_ERROR(error);
return command; return command;
} }
if (!chatterino::rj::getSafe(value, "name", command.name)) if (!chatterino::rj::getSafe(value, "name", command.name))
{ {
PAJLADA_REPORT_ERROR(error); PAJLADA_REPORT_ERROR(error);
return command; return command;
} }
if (!chatterino::rj::getSafe(value, "func", command.func)) if (!chatterino::rj::getSafe(value, "func", command.func))
{ {
PAJLADA_REPORT_ERROR(error); PAJLADA_REPORT_ERROR(error);
return command; return command;
} }
return command; return command;
} }
}; };
} // namespace pajlada } // namespace pajlada

File diff suppressed because it is too large Load diff

View file

@ -1,56 +1,56 @@
#pragma once #pragma once
#include "common/ChatterinoSetting.hpp" #include "common/ChatterinoSetting.hpp"
#include "common/SignalVector.hpp" #include "common/SignalVector.hpp"
#include "common/Singleton.hpp" #include "common/Singleton.hpp"
#include "controllers/commands/Command.hpp" #include "controllers/commands/Command.hpp"
#include <QMap> #include <QMap>
#include <pajlada/settings.hpp> #include <pajlada/settings.hpp>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
namespace chatterino { namespace chatterino {
class Settings; class Settings;
class Paths; class Paths;
class Channel; class Channel;
class CommandModel; class CommandModel;
class CommandController final : public Singleton class CommandController final : public Singleton
{ {
public: public:
UnsortedSignalVector<Command> items_; UnsortedSignalVector<Command> items_;
QString execCommand(const QString &text, std::shared_ptr<Channel> channel, QString execCommand(const QString &text, std::shared_ptr<Channel> channel,
bool dryRun); bool dryRun);
QStringList getDefaultTwitchCommandList(); QStringList getDefaultTwitchCommandList();
virtual void initialize(Settings &, Paths &paths) override; virtual void initialize(Settings &, Paths &paths) override;
virtual void save() override; virtual void save() override;
CommandModel *createModel(QObject *parent); CommandModel *createModel(QObject *parent);
private: private:
void load(Paths &paths); void load(Paths &paths);
QMap<QString, Command> commandsMap_; QMap<QString, Command> commandsMap_;
int maxSpaces_ = 0; int maxSpaces_ = 0;
std::mutex mutex_; std::mutex mutex_;
std::shared_ptr<pajlada::Settings::SettingManager> sm_; std::shared_ptr<pajlada::Settings::SettingManager> sm_;
// Because the setting manager is not initialized until the initialize // Because the setting manager is not initialized until the initialize
// function is called (and not in the constructor), we have to // function is called (and not in the constructor), we have to
// late-initialize the setting, which is why we're storing it as a // late-initialize the setting, which is why we're storing it as a
// unique_ptr // unique_ptr
std::unique_ptr<pajlada::Settings::Setting<std::vector<Command>>> std::unique_ptr<pajlada::Settings::Setting<std::vector<Command>>>
commandsSetting_; commandsSetting_;
QString execCustomCommand(const QStringList &words, const Command &command, QString execCustomCommand(const QStringList &words, const Command &command,
bool dryRun); bool dryRun);
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,31 +1,31 @@
#include "CommandModel.hpp" #include "CommandModel.hpp"
namespace chatterino { namespace chatterino {
// commandmodel // commandmodel
CommandModel::CommandModel(QObject *parent) CommandModel::CommandModel(QObject *parent)
: SignalVectorModel<Command>(2, parent) : SignalVectorModel<Command>(2, parent)
{ {
} }
// turn a vector item into a model row // turn a vector item into a model row
Command CommandModel::getItemFromRow(std::vector<QStandardItem *> &row, Command CommandModel::getItemFromRow(std::vector<QStandardItem *> &row,
const Command &original) const Command &original)
{ {
return Command(row[0]->data(Qt::EditRole).toString(), return Command(row[0]->data(Qt::EditRole).toString(),
row[1]->data(Qt::EditRole).toString()); row[1]->data(Qt::EditRole).toString());
} }
// turns a row in the model into a vector item // turns a row in the model into a vector item
void CommandModel::getRowFromItem(const Command &item, void CommandModel::getRowFromItem(const Command &item,
std::vector<QStandardItem *> &row) std::vector<QStandardItem *> &row)
{ {
row[0]->setData(item.name, Qt::DisplayRole); row[0]->setData(item.name, Qt::DisplayRole);
row[0]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | row[0]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable |
Qt::ItemIsEditable); Qt::ItemIsEditable);
row[1]->setData(item.func, Qt::DisplayRole); row[1]->setData(item.func, Qt::DisplayRole);
row[1]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | row[1]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable |
Qt::ItemIsEditable); Qt::ItemIsEditable);
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,28 +1,28 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include "common/SignalVectorModel.hpp" #include "common/SignalVectorModel.hpp"
#include "controllers/commands/Command.hpp" #include "controllers/commands/Command.hpp"
namespace chatterino { namespace chatterino {
class CommandController; class CommandController;
class CommandModel : public SignalVectorModel<Command> class CommandModel : public SignalVectorModel<Command>
{ {
explicit CommandModel(QObject *parent); explicit CommandModel(QObject *parent);
protected: protected:
// turn a vector item into a model row // turn a vector item into a model row
virtual Command getItemFromRow(std::vector<QStandardItem *> &row, virtual Command getItemFromRow(std::vector<QStandardItem *> &row,
const Command &command) override; const Command &command) override;
// turns a row in the model into a vector item // turns a row in the model into a vector item
virtual void getRowFromItem(const Command &item, virtual void getRowFromItem(const Command &item,
std::vector<QStandardItem *> &row) override; std::vector<QStandardItem *> &row) override;
friend class CommandController; friend class CommandController;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,110 +1,110 @@
#include "HighlightController.hpp" #include "HighlightController.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "controllers/highlights/HighlightBlacklistModel.hpp" #include "controllers/highlights/HighlightBlacklistModel.hpp"
#include "controllers/highlights/HighlightModel.hpp" #include "controllers/highlights/HighlightModel.hpp"
#include "controllers/highlights/UserHighlightModel.hpp" #include "controllers/highlights/UserHighlightModel.hpp"
#include "widgets/dialogs/NotificationPopup.hpp" #include "widgets/dialogs/NotificationPopup.hpp"
namespace chatterino { namespace chatterino {
HighlightController::HighlightController() HighlightController::HighlightController()
{ {
} }
void HighlightController::initialize(Settings &settings, Paths &paths) void HighlightController::initialize(Settings &settings, Paths &paths)
{ {
assert(!this->initialized_); assert(!this->initialized_);
this->initialized_ = true; this->initialized_ = true;
for (const HighlightPhrase &phrase : this->highlightsSetting_.getValue()) for (const HighlightPhrase &phrase : this->highlightsSetting_.getValue())
{ {
this->phrases.appendItem(phrase); this->phrases.appendItem(phrase);
} }
this->phrases.delayedItemsChanged.connect([this] { // this->phrases.delayedItemsChanged.connect([this] { //
this->highlightsSetting_.setValue(this->phrases.getVector()); this->highlightsSetting_.setValue(this->phrases.getVector());
}); });
for (const HighlightBlacklistUser &blacklistedUser : for (const HighlightBlacklistUser &blacklistedUser :
this->blacklistSetting_.getValue()) this->blacklistSetting_.getValue())
{ {
this->blacklistedUsers.appendItem(blacklistedUser); this->blacklistedUsers.appendItem(blacklistedUser);
} }
this->blacklistedUsers.delayedItemsChanged.connect([this] { this->blacklistedUsers.delayedItemsChanged.connect([this] {
this->blacklistSetting_.setValue(this->blacklistedUsers.getVector()); this->blacklistSetting_.setValue(this->blacklistedUsers.getVector());
}); });
for (const HighlightPhrase &user : this->userSetting_.getValue()) for (const HighlightPhrase &user : this->userSetting_.getValue())
{ {
this->highlightedUsers.appendItem(user); this->highlightedUsers.appendItem(user);
} }
this->highlightedUsers.delayedItemsChanged.connect([this] { // this->highlightedUsers.delayedItemsChanged.connect([this] { //
this->userSetting_.setValue(this->highlightedUsers.getVector()); this->userSetting_.setValue(this->highlightedUsers.getVector());
}); });
} }
HighlightModel *HighlightController::createModel(QObject *parent) HighlightModel *HighlightController::createModel(QObject *parent)
{ {
HighlightModel *model = new HighlightModel(parent); HighlightModel *model = new HighlightModel(parent);
model->init(&this->phrases); model->init(&this->phrases);
return model; return model;
} }
UserHighlightModel *HighlightController::createUserModel(QObject *parent) UserHighlightModel *HighlightController::createUserModel(QObject *parent)
{ {
auto *model = new UserHighlightModel(parent); auto *model = new UserHighlightModel(parent);
model->init(&this->highlightedUsers); model->init(&this->highlightedUsers);
return model; return model;
} }
bool HighlightController::isHighlightedUser(const QString &username) bool HighlightController::isHighlightedUser(const QString &username)
{ {
const auto &userItems = this->highlightedUsers; const auto &userItems = this->highlightedUsers;
for (const auto &highlightedUser : userItems) for (const auto &highlightedUser : userItems)
{ {
if (highlightedUser.isMatch(username)) if (highlightedUser.isMatch(username))
{ {
return true; return true;
} }
} }
return false; return false;
} }
HighlightBlacklistModel *HighlightController::createBlacklistModel( HighlightBlacklistModel *HighlightController::createBlacklistModel(
QObject *parent) QObject *parent)
{ {
auto *model = new HighlightBlacklistModel(parent); auto *model = new HighlightBlacklistModel(parent);
model->init(&this->blacklistedUsers); model->init(&this->blacklistedUsers);
return model; return model;
} }
bool HighlightController::blacklistContains(const QString &username) bool HighlightController::blacklistContains(const QString &username)
{ {
for (const auto &blacklistedUser : this->blacklistedUsers) for (const auto &blacklistedUser : this->blacklistedUsers)
{ {
if (blacklistedUser.isMatch(username)) if (blacklistedUser.isMatch(username))
{ {
return true; return true;
} }
} }
return false; return false;
} }
void HighlightController::addHighlight(const MessagePtr &msg) void HighlightController::addHighlight(const MessagePtr &msg)
{ {
// static NotificationPopup popup; // static NotificationPopup popup;
// popup.updatePosition(); // popup.updatePosition();
// popup.addMessage(msg); // popup.addMessage(msg);
// popup.show(); // popup.show();
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,52 +1,52 @@
#pragma once #pragma once
#include "common/ChatterinoSetting.hpp" #include "common/ChatterinoSetting.hpp"
#include "common/SignalVector.hpp" #include "common/SignalVector.hpp"
#include "common/Singleton.hpp" #include "common/Singleton.hpp"
#include "controllers/highlights/HighlightBlacklistUser.hpp" #include "controllers/highlights/HighlightBlacklistUser.hpp"
#include "controllers/highlights/HighlightPhrase.hpp" #include "controllers/highlights/HighlightPhrase.hpp"
namespace chatterino { namespace chatterino {
struct Message; struct Message;
using MessagePtr = std::shared_ptr<const Message>; using MessagePtr = std::shared_ptr<const Message>;
class Settings; class Settings;
class Paths; class Paths;
class UserHighlightModel; class UserHighlightModel;
class HighlightModel; class HighlightModel;
class HighlightBlacklistModel; class HighlightBlacklistModel;
class HighlightController final : public Singleton class HighlightController final : public Singleton
{ {
public: public:
HighlightController(); HighlightController();
virtual void initialize(Settings &settings, Paths &paths) override; virtual void initialize(Settings &settings, Paths &paths) override;
UnsortedSignalVector<HighlightPhrase> phrases; UnsortedSignalVector<HighlightPhrase> phrases;
UnsortedSignalVector<HighlightBlacklistUser> blacklistedUsers; UnsortedSignalVector<HighlightBlacklistUser> blacklistedUsers;
UnsortedSignalVector<HighlightPhrase> highlightedUsers; UnsortedSignalVector<HighlightPhrase> highlightedUsers;
HighlightModel *createModel(QObject *parent); HighlightModel *createModel(QObject *parent);
HighlightBlacklistModel *createBlacklistModel(QObject *parent); HighlightBlacklistModel *createBlacklistModel(QObject *parent);
UserHighlightModel *createUserModel(QObject *parent); UserHighlightModel *createUserModel(QObject *parent);
bool isHighlightedUser(const QString &username); bool isHighlightedUser(const QString &username);
bool blacklistContains(const QString &username); bool blacklistContains(const QString &username);
void addHighlight(const MessagePtr &msg); void addHighlight(const MessagePtr &msg);
private: private:
bool initialized_ = false; bool initialized_ = false;
ChatterinoSetting<std::vector<HighlightPhrase>> highlightsSetting_ = { ChatterinoSetting<std::vector<HighlightPhrase>> highlightsSetting_ = {
"/highlighting/highlights"}; "/highlighting/highlights"};
ChatterinoSetting<std::vector<HighlightBlacklistUser>> blacklistSetting_ = { ChatterinoSetting<std::vector<HighlightBlacklistUser>> blacklistSetting_ = {
"/highlighting/blacklist"}; "/highlighting/blacklist"};
ChatterinoSetting<std::vector<HighlightPhrase>> userSetting_ = { ChatterinoSetting<std::vector<HighlightPhrase>> userSetting_ = {
"/highlighting/users"}; "/highlighting/users"};
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,131 +1,131 @@
#include "HighlightModel.hpp" #include "HighlightModel.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
#include "util/StandardItemHelper.hpp" #include "util/StandardItemHelper.hpp"
namespace chatterino { namespace chatterino {
// commandmodel // commandmodel
HighlightModel::HighlightModel(QObject *parent) HighlightModel::HighlightModel(QObject *parent)
: SignalVectorModel<HighlightPhrase>(5, parent) : SignalVectorModel<HighlightPhrase>(5, parent)
{ {
} }
// turn a vector item into a model row // turn a vector item into a model row
HighlightPhrase HighlightModel::getItemFromRow( HighlightPhrase HighlightModel::getItemFromRow(
std::vector<QStandardItem *> &row, const HighlightPhrase &original) std::vector<QStandardItem *> &row, const HighlightPhrase &original)
{ {
// key, alert, sound, regex, case-sensitivity // key, alert, sound, regex, case-sensitivity
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(), return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
row[1]->data(Qt::CheckStateRole).toBool(), row[1]->data(Qt::CheckStateRole).toBool(),
row[2]->data(Qt::CheckStateRole).toBool(), row[2]->data(Qt::CheckStateRole).toBool(),
row[3]->data(Qt::CheckStateRole).toBool(), row[3]->data(Qt::CheckStateRole).toBool(),
row[4]->data(Qt::CheckStateRole).toBool()}; row[4]->data(Qt::CheckStateRole).toBool()};
} }
// turns a row in the model into a vector item // turns a row in the model into a vector item
void HighlightModel::getRowFromItem(const HighlightPhrase &item, void HighlightModel::getRowFromItem(const HighlightPhrase &item,
std::vector<QStandardItem *> &row) std::vector<QStandardItem *> &row)
{ {
setStringItem(row[0], item.getPattern()); setStringItem(row[0], item.getPattern());
setBoolItem(row[1], item.getAlert()); setBoolItem(row[1], item.getAlert());
setBoolItem(row[2], item.getSound()); setBoolItem(row[2], item.getSound());
setBoolItem(row[3], item.isRegex()); setBoolItem(row[3], item.isRegex());
setBoolItem(row[4], item.isCaseSensitive()); setBoolItem(row[4], item.isCaseSensitive());
} }
void HighlightModel::afterInit() void HighlightModel::afterInit()
{ {
std::vector<QStandardItem *> usernameRow = this->createRow(); std::vector<QStandardItem *> usernameRow = this->createRow();
setBoolItem(usernameRow[0], getSettings()->enableSelfHighlight.getValue(), setBoolItem(usernameRow[0], getSettings()->enableSelfHighlight.getValue(),
true, false); true, false);
usernameRow[0]->setData("Your username (automatic)", Qt::DisplayRole); usernameRow[0]->setData("Your username (automatic)", Qt::DisplayRole);
setBoolItem(usernameRow[1], setBoolItem(usernameRow[1],
getSettings()->enableSelfHighlightTaskbar.getValue(), true, getSettings()->enableSelfHighlightTaskbar.getValue(), true,
false); false);
setBoolItem(usernameRow[2], setBoolItem(usernameRow[2],
getSettings()->enableSelfHighlightSound.getValue(), true, getSettings()->enableSelfHighlightSound.getValue(), true,
false); false);
usernameRow[3]->setFlags(0); usernameRow[3]->setFlags(0);
this->insertCustomRow(usernameRow, 0); this->insertCustomRow(usernameRow, 0);
std::vector<QStandardItem *> whisperRow = this->createRow(); std::vector<QStandardItem *> whisperRow = this->createRow();
setBoolItem(whisperRow[0], getSettings()->enableWhisperHighlight.getValue(), setBoolItem(whisperRow[0], getSettings()->enableWhisperHighlight.getValue(),
true, false); true, false);
whisperRow[0]->setData("Whispers", Qt::DisplayRole); whisperRow[0]->setData("Whispers", Qt::DisplayRole);
setBoolItem(whisperRow[1], setBoolItem(whisperRow[1],
getSettings()->enableWhisperHighlightTaskbar.getValue(), true, getSettings()->enableWhisperHighlightTaskbar.getValue(), true,
false); false);
setBoolItem(whisperRow[2], setBoolItem(whisperRow[2],
getSettings()->enableWhisperHighlightSound.getValue(), true, getSettings()->enableWhisperHighlightSound.getValue(), true,
false); false);
whisperRow[3]->setFlags(0); whisperRow[3]->setFlags(0);
this->insertCustomRow(whisperRow, 1); this->insertCustomRow(whisperRow, 1);
} }
void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row, void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
int column, const QVariant &value, int column, const QVariant &value,
int role, int rowIndex) int role, int rowIndex)
{ {
switch (column) switch (column)
{ {
case 0: case 0:
{ {
if (role == Qt::CheckStateRole) if (role == Qt::CheckStateRole)
{ {
if (rowIndex == 0) if (rowIndex == 0)
{ {
getSettings()->enableSelfHighlight.setValue(value.toBool()); getSettings()->enableSelfHighlight.setValue(value.toBool());
} }
else if (rowIndex == 1) else if (rowIndex == 1)
{ {
getSettings()->enableWhisperHighlight.setValue( getSettings()->enableWhisperHighlight.setValue(
value.toBool()); value.toBool());
} }
} }
} }
break; break;
case 1: case 1:
{ {
if (role == Qt::CheckStateRole) if (role == Qt::CheckStateRole)
{ {
if (rowIndex == 0) if (rowIndex == 0)
{ {
getSettings()->enableSelfHighlightTaskbar.setValue( getSettings()->enableSelfHighlightTaskbar.setValue(
value.toBool()); value.toBool());
} }
else if (rowIndex == 1) else if (rowIndex == 1)
{ {
getSettings()->enableWhisperHighlightTaskbar.setValue( getSettings()->enableWhisperHighlightTaskbar.setValue(
value.toBool()); value.toBool());
} }
} }
} }
break; break;
case 2: case 2:
{ {
if (role == Qt::CheckStateRole) if (role == Qt::CheckStateRole)
{ {
if (rowIndex == 0) if (rowIndex == 0)
{ {
getSettings()->enableSelfHighlightSound.setValue( getSettings()->enableSelfHighlightSound.setValue(
value.toBool()); value.toBool());
} }
else if (rowIndex == 1) else if (rowIndex == 1)
{ {
getSettings()->enableWhisperHighlightSound.setValue( getSettings()->enableWhisperHighlightSound.setValue(
value.toBool()); value.toBool());
} }
} }
} }
break; break;
case 3: case 3:
{ {
// empty element // empty element
} }
break; break;
} }
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,35 +1,35 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include "common/SignalVectorModel.hpp" #include "common/SignalVectorModel.hpp"
#include "controllers/highlights/HighlightPhrase.hpp" #include "controllers/highlights/HighlightPhrase.hpp"
namespace chatterino { namespace chatterino {
class HighlightController; class HighlightController;
class HighlightModel : public SignalVectorModel<HighlightPhrase> class HighlightModel : public SignalVectorModel<HighlightPhrase>
{ {
explicit HighlightModel(QObject *parent); explicit HighlightModel(QObject *parent);
protected: protected:
// turn a vector item into a model row // turn a vector item into a model row
virtual HighlightPhrase getItemFromRow( virtual HighlightPhrase getItemFromRow(
std::vector<QStandardItem *> &row, std::vector<QStandardItem *> &row,
const HighlightPhrase &original) override; const HighlightPhrase &original) override;
// turns a row in the model into a vector item // turns a row in the model into a vector item
virtual void getRowFromItem(const HighlightPhrase &item, virtual void getRowFromItem(const HighlightPhrase &item,
std::vector<QStandardItem *> &row) override; std::vector<QStandardItem *> &row) override;
virtual void afterInit() override; virtual void afterInit() override;
virtual void customRowSetData(const std::vector<QStandardItem *> &row, virtual void customRowSetData(const std::vector<QStandardItem *> &row,
int column, const QVariant &value, int role, int column, const QVariant &value, int role,
int rowIndex) override; int rowIndex) override;
friend class HighlightController; friend class HighlightController;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,127 +1,127 @@
#pragma once #pragma once
#include "util/RapidJsonSerializeQString.hpp" #include "util/RapidJsonSerializeQString.hpp"
#include "util/RapidjsonHelpers.hpp" #include "util/RapidjsonHelpers.hpp"
#include <QRegularExpression> #include <QRegularExpression>
#include <QString> #include <QString>
#include <pajlada/serialize.hpp> #include <pajlada/serialize.hpp>
namespace chatterino { namespace chatterino {
class HighlightPhrase class HighlightPhrase
{ {
public: public:
bool operator==(const HighlightPhrase &other) const bool operator==(const HighlightPhrase &other) const
{ {
return std::tie(this->pattern_, this->sound_, this->alert_, return std::tie(this->pattern_, this->sound_, this->alert_,
this->isRegex_, this->caseSensitive_) == this->isRegex_, this->caseSensitive_) ==
std::tie(other.pattern_, other.sound_, other.alert_, std::tie(other.pattern_, other.sound_, other.alert_,
other.isRegex_, other.caseSensitive_); other.isRegex_, other.caseSensitive_);
} }
HighlightPhrase(const QString &pattern, bool alert, bool sound, HighlightPhrase(const QString &pattern, bool alert, bool sound,
bool isRegex, bool caseSensitive) bool isRegex, bool caseSensitive)
: pattern_(pattern) : pattern_(pattern)
, alert_(alert) , alert_(alert)
, sound_(sound) , sound_(sound)
, isRegex_(isRegex) , isRegex_(isRegex)
, caseSensitive_(caseSensitive) , caseSensitive_(caseSensitive)
, regex_( , regex_(
isRegex_ ? pattern isRegex_ ? pattern
: "\\b" + QRegularExpression::escape(pattern) + "\\b", : "\\b" + QRegularExpression::escape(pattern) + "\\b",
QRegularExpression::UseUnicodePropertiesOption | QRegularExpression::UseUnicodePropertiesOption |
(caseSensitive_ ? QRegularExpression::NoPatternOption (caseSensitive_ ? QRegularExpression::NoPatternOption
: QRegularExpression::CaseInsensitiveOption)) : QRegularExpression::CaseInsensitiveOption))
{ {
} }
const QString &getPattern() const const QString &getPattern() const
{ {
return this->pattern_; return this->pattern_;
} }
bool getAlert() const bool getAlert() const
{ {
return this->alert_; return this->alert_;
} }
bool getSound() const bool getSound() const
{ {
return this->sound_; return this->sound_;
} }
bool isRegex() const bool isRegex() const
{ {
return this->isRegex_; return this->isRegex_;
} }
bool isValid() const bool isValid() const
{ {
return !this->pattern_.isEmpty() && this->regex_.isValid(); return !this->pattern_.isEmpty() && this->regex_.isValid();
} }
bool isMatch(const QString &subject) const bool isMatch(const QString &subject) const
{ {
return this->isValid() && this->regex_.match(subject).hasMatch(); return this->isValid() && this->regex_.match(subject).hasMatch();
} }
bool isCaseSensitive() const bool isCaseSensitive() const
{ {
return this->caseSensitive_; return this->caseSensitive_;
} }
private: private:
QString pattern_; QString pattern_;
bool alert_; bool alert_;
bool sound_; bool sound_;
bool isRegex_; bool isRegex_;
bool caseSensitive_; bool caseSensitive_;
QRegularExpression regex_; QRegularExpression regex_;
}; };
} // namespace chatterino } // namespace chatterino
namespace pajlada { namespace pajlada {
template <> template <>
struct Serialize<chatterino::HighlightPhrase> { struct Serialize<chatterino::HighlightPhrase> {
static rapidjson::Value get(const chatterino::HighlightPhrase &value, static rapidjson::Value get(const chatterino::HighlightPhrase &value,
rapidjson::Document::AllocatorType &a) rapidjson::Document::AllocatorType &a)
{ {
rapidjson::Value ret(rapidjson::kObjectType); rapidjson::Value ret(rapidjson::kObjectType);
chatterino::rj::set(ret, "pattern", value.getPattern(), a); chatterino::rj::set(ret, "pattern", value.getPattern(), a);
chatterino::rj::set(ret, "alert", value.getAlert(), a); chatterino::rj::set(ret, "alert", value.getAlert(), a);
chatterino::rj::set(ret, "sound", value.getSound(), a); chatterino::rj::set(ret, "sound", value.getSound(), a);
chatterino::rj::set(ret, "regex", value.isRegex(), a); chatterino::rj::set(ret, "regex", value.isRegex(), a);
chatterino::rj::set(ret, "case", value.isCaseSensitive(), a); chatterino::rj::set(ret, "case", value.isCaseSensitive(), a);
return ret; return ret;
} }
}; };
template <> template <>
struct Deserialize<chatterino::HighlightPhrase> { struct Deserialize<chatterino::HighlightPhrase> {
static chatterino::HighlightPhrase get(const rapidjson::Value &value) static chatterino::HighlightPhrase get(const rapidjson::Value &value)
{ {
if (!value.IsObject()) if (!value.IsObject())
{ {
return chatterino::HighlightPhrase(QString(), true, false, false, return chatterino::HighlightPhrase(QString(), true, false, false,
false); false);
} }
QString _pattern; QString _pattern;
bool _alert = true; bool _alert = true;
bool _sound = false; bool _sound = false;
bool _isRegex = false; bool _isRegex = false;
bool _caseSensitive = false; bool _caseSensitive = false;
chatterino::rj::getSafe(value, "pattern", _pattern); chatterino::rj::getSafe(value, "pattern", _pattern);
chatterino::rj::getSafe(value, "alert", _alert); chatterino::rj::getSafe(value, "alert", _alert);
chatterino::rj::getSafe(value, "sound", _sound); chatterino::rj::getSafe(value, "sound", _sound);
chatterino::rj::getSafe(value, "regex", _isRegex); chatterino::rj::getSafe(value, "regex", _isRegex);
chatterino::rj::getSafe(value, "case", _caseSensitive); chatterino::rj::getSafe(value, "case", _caseSensitive);
return chatterino::HighlightPhrase(_pattern, _alert, _sound, return chatterino::HighlightPhrase(_pattern, _alert, _sound,
_isRegex, _caseSensitive); _isRegex, _caseSensitive);
} }
}; };
} // namespace pajlada } // namespace pajlada

View file

@ -1,121 +1,121 @@
#include "ModerationAction.hpp" #include "ModerationAction.hpp"
#include <QRegularExpression> #include <QRegularExpression>
#include "Application.hpp" #include "Application.hpp"
#include "messages/Image.hpp" #include "messages/Image.hpp"
#include "singletons/Resources.hpp" #include "singletons/Resources.hpp"
namespace chatterino { namespace chatterino {
// ModerationAction::ModerationAction(Image *_image, const QString &_action) // ModerationAction::ModerationAction(Image *_image, const QString &_action)
// : _isImage(true) // : _isImage(true)
// , image(_image) // , image(_image)
// , action(_action) // , action(_action)
//{ //{
//} //}
// ModerationAction::ModerationAction(const QString &_line1, const QString // ModerationAction::ModerationAction(const QString &_line1, const QString
// &_line2, // &_line2,
// const QString &_action) // const QString &_action)
// : _isImage(false) // : _isImage(false)
// , image(nullptr) // , image(nullptr)
// , line1(_line1) // , line1(_line1)
// , line2(_line2) // , line2(_line2)
// , action(_action) // , action(_action)
//{ //{
//} //}
ModerationAction::ModerationAction(const QString &action) ModerationAction::ModerationAction(const QString &action)
: action_(action) : action_(action)
{ {
static QRegularExpression replaceRegex("[!/.]"); static QRegularExpression replaceRegex("[!/.]");
static QRegularExpression timeoutRegex("^[./]timeout.* (\\d+)"); static QRegularExpression timeoutRegex("^[./]timeout.* (\\d+)");
auto timeoutMatch = timeoutRegex.match(action); auto timeoutMatch = timeoutRegex.match(action);
if (timeoutMatch.hasMatch()) if (timeoutMatch.hasMatch())
{ {
// if (multipleTimeouts > 1) { // if (multipleTimeouts > 1) {
// QString line1; // QString line1;
// QString line2; // QString line2;
int amount = timeoutMatch.captured(1).toInt(); int amount = timeoutMatch.captured(1).toInt();
if (amount < 60) if (amount < 60)
{ {
this->line1_ = QString::number(amount); this->line1_ = QString::number(amount);
this->line2_ = "s"; this->line2_ = "s";
} }
else if (amount < 60 * 60) else if (amount < 60 * 60)
{ {
this->line1_ = QString::number(amount / 60); this->line1_ = QString::number(amount / 60);
this->line2_ = "m"; this->line2_ = "m";
} }
else if (amount < 60 * 60 * 24) else if (amount < 60 * 60 * 24)
{ {
this->line1_ = QString::number(amount / 60 / 60); this->line1_ = QString::number(amount / 60 / 60);
this->line2_ = "h"; this->line2_ = "h";
} }
else else
{ {
this->line1_ = QString::number(amount / 60 / 60 / 24); this->line1_ = QString::number(amount / 60 / 60 / 24);
this->line2_ = "d"; this->line2_ = "d";
} }
// line1 = this->line1_; // line1 = this->line1_;
// line2 = this->line2_; // line2 = this->line2_;
// } else { // } else {
// this->_moderationActions.emplace_back(app->resources->buttonTimeout, // this->_moderationActions.emplace_back(app->resources->buttonTimeout,
// str); // str);
// } // }
} }
else if (action.startsWith("/ban ")) else if (action.startsWith("/ban "))
{ {
this->image_ = Image::fromPixmap(getApp()->resources->buttons.ban); this->image_ = Image::fromPixmap(getApp()->resources->buttons.ban);
} }
else if (action.startsWith("/delete ")) else if (action.startsWith("/delete "))
{ {
this->image_ = Image::fromPixmap(getApp()->resources->buttons.trashCan); this->image_ = Image::fromPixmap(getApp()->resources->buttons.trashCan);
} }
else else
{ {
QString xD = action; QString xD = action;
xD.replace(replaceRegex, ""); xD.replace(replaceRegex, "");
this->line1_ = xD.mid(0, 2); this->line1_ = xD.mid(0, 2);
this->line2_ = xD.mid(2, 2); this->line2_ = xD.mid(2, 2);
} }
} }
bool ModerationAction::operator==(const ModerationAction &other) const bool ModerationAction::operator==(const ModerationAction &other) const
{ {
return this == std::addressof(other); return this == std::addressof(other);
} }
bool ModerationAction::isImage() const bool ModerationAction::isImage() const
{ {
return bool(this->image_); return bool(this->image_);
} }
const boost::optional<ImagePtr> &ModerationAction::getImage() const const boost::optional<ImagePtr> &ModerationAction::getImage() const
{ {
return this->image_; return this->image_;
} }
const QString &ModerationAction::getLine1() const const QString &ModerationAction::getLine1() const
{ {
return this->line1_; return this->line1_;
} }
const QString &ModerationAction::getLine2() const const QString &ModerationAction::getLine2() const
{ {
return this->line2_; return this->line2_;
} }
const QString &ModerationAction::getAction() const const QString &ModerationAction::getAction() const
{ {
return this->action_; return this->action_;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,68 +1,68 @@
#pragma once #pragma once
#include <QString> #include <QString>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <pajlada/serialize.hpp> #include <pajlada/serialize.hpp>
#include "util/RapidjsonHelpers.hpp" #include "util/RapidjsonHelpers.hpp"
namespace chatterino { namespace chatterino {
class Image; class Image;
using ImagePtr = std::shared_ptr<Image>; using ImagePtr = std::shared_ptr<Image>;
class ModerationAction class ModerationAction
{ {
public: public:
ModerationAction(const QString &action); ModerationAction(const QString &action);
bool operator==(const ModerationAction &other) const; bool operator==(const ModerationAction &other) const;
bool isImage() const; bool isImage() const;
const boost::optional<ImagePtr> &getImage() const; const boost::optional<ImagePtr> &getImage() const;
const QString &getLine1() const; const QString &getLine1() const;
const QString &getLine2() const; const QString &getLine2() const;
const QString &getAction() const; const QString &getAction() const;
private: private:
boost::optional<ImagePtr> image_; boost::optional<ImagePtr> image_;
QString line1_; QString line1_;
QString line2_; QString line2_;
QString action_; QString action_;
}; };
} // namespace chatterino } // namespace chatterino
namespace pajlada { namespace pajlada {
template <> template <>
struct Serialize<chatterino::ModerationAction> { struct Serialize<chatterino::ModerationAction> {
static rapidjson::Value get(const chatterino::ModerationAction &value, static rapidjson::Value get(const chatterino::ModerationAction &value,
rapidjson::Document::AllocatorType &a) rapidjson::Document::AllocatorType &a)
{ {
rapidjson::Value ret(rapidjson::kObjectType); rapidjson::Value ret(rapidjson::kObjectType);
chatterino::rj::set(ret, "pattern", value.getAction(), a); chatterino::rj::set(ret, "pattern", value.getAction(), a);
return ret; return ret;
} }
}; };
template <> template <>
struct Deserialize<chatterino::ModerationAction> { struct Deserialize<chatterino::ModerationAction> {
static chatterino::ModerationAction get(const rapidjson::Value &value) static chatterino::ModerationAction get(const rapidjson::Value &value)
{ {
if (!value.IsObject()) if (!value.IsObject())
{ {
return chatterino::ModerationAction(QString()); return chatterino::ModerationAction(QString());
} }
QString pattern; QString pattern;
chatterino::rj::getSafe(value, "pattern", pattern); chatterino::rj::getSafe(value, "pattern", pattern);
return chatterino::ModerationAction(pattern); return chatterino::ModerationAction(pattern);
} }
}; };
} // namespace pajlada } // namespace pajlada

View file

@ -1,27 +1,27 @@
#include "ModerationActionModel.hpp" #include "ModerationActionModel.hpp"
#include "util/StandardItemHelper.hpp" #include "util/StandardItemHelper.hpp"
namespace chatterino { namespace chatterino {
// commandmodel // commandmodel
ModerationActionModel ::ModerationActionModel(QObject *parent) ModerationActionModel ::ModerationActionModel(QObject *parent)
: SignalVectorModel<ModerationAction>(1, parent) : SignalVectorModel<ModerationAction>(1, parent)
{ {
} }
// turn a vector item into a model row // turn a vector item into a model row
ModerationAction ModerationActionModel::getItemFromRow( ModerationAction ModerationActionModel::getItemFromRow(
std::vector<QStandardItem *> &row, const ModerationAction &original) std::vector<QStandardItem *> &row, const ModerationAction &original)
{ {
return ModerationAction(row[0]->data(Qt::DisplayRole).toString()); return ModerationAction(row[0]->data(Qt::DisplayRole).toString());
} }
// turns a row in the model into a vector item // turns a row in the model into a vector item
void ModerationActionModel::getRowFromItem(const ModerationAction &item, void ModerationActionModel::getRowFromItem(const ModerationAction &item,
std::vector<QStandardItem *> &row) std::vector<QStandardItem *> &row)
{ {
setStringItem(row[0], item.getAction()); setStringItem(row[0], item.getAction());
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,32 +1,32 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include "common/SignalVectorModel.hpp" #include "common/SignalVectorModel.hpp"
#include "controllers/moderationactions/ModerationAction.hpp" #include "controllers/moderationactions/ModerationAction.hpp"
namespace chatterino { namespace chatterino {
class ModerationActions; class ModerationActions;
class ModerationActionModel : public SignalVectorModel<ModerationAction> class ModerationActionModel : public SignalVectorModel<ModerationAction>
{ {
public: public:
explicit ModerationActionModel(QObject *parent); explicit ModerationActionModel(QObject *parent);
protected: protected:
// turn a vector item into a model row // turn a vector item into a model row
virtual ModerationAction getItemFromRow( virtual ModerationAction getItemFromRow(
std::vector<QStandardItem *> &row, std::vector<QStandardItem *> &row,
const ModerationAction &original) override; const ModerationAction &original) override;
// turns a row in the model into a vector item // turns a row in the model into a vector item
virtual void getRowFromItem(const ModerationAction &item, virtual void getRowFromItem(const ModerationAction &item,
std::vector<QStandardItem *> &row) override; std::vector<QStandardItem *> &row) override;
friend class HighlightController; friend class HighlightController;
friend class ModerationActions; friend class ModerationActions;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,42 +1,42 @@
#include "ModerationActions.hpp" #include "ModerationActions.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "controllers/moderationactions/ModerationActionModel.hpp" #include "controllers/moderationactions/ModerationActionModel.hpp"
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
#include <QRegularExpression> #include <QRegularExpression>
namespace chatterino { namespace chatterino {
ModerationActions::ModerationActions() ModerationActions::ModerationActions()
{ {
} }
void ModerationActions::initialize(Settings &settings, Paths &paths) void ModerationActions::initialize(Settings &settings, Paths &paths)
{ {
assert(!this->initialized_); assert(!this->initialized_);
this->initialized_ = true; this->initialized_ = true;
this->setting_ = this->setting_ =
std::make_unique<ChatterinoSetting<std::vector<ModerationAction>>>( std::make_unique<ChatterinoSetting<std::vector<ModerationAction>>>(
"/moderation/actions"); "/moderation/actions");
for (auto &val : this->setting_->getValue()) for (auto &val : this->setting_->getValue())
{ {
this->items.insertItem(val); this->items.insertItem(val);
} }
this->items.delayedItemsChanged.connect([this] { // this->items.delayedItemsChanged.connect([this] { //
this->setting_->setValue(this->items.getVector()); this->setting_->setValue(this->items.getVector());
}); });
} }
ModerationActionModel *ModerationActions::createModel(QObject *parent) ModerationActionModel *ModerationActions::createModel(QObject *parent)
{ {
ModerationActionModel *model = new ModerationActionModel(parent); ModerationActionModel *model = new ModerationActionModel(parent);
model->init(&this->items); model->init(&this->items);
return model; return model;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,32 +1,32 @@
#pragma once #pragma once
#include "common/Singleton.hpp" #include "common/Singleton.hpp"
#include "common/ChatterinoSetting.hpp" #include "common/ChatterinoSetting.hpp"
#include "common/SignalVector.hpp" #include "common/SignalVector.hpp"
#include "controllers/moderationactions/ModerationAction.hpp" #include "controllers/moderationactions/ModerationAction.hpp"
namespace chatterino { namespace chatterino {
class Settings; class Settings;
class Paths; class Paths;
class ModerationActionModel; class ModerationActionModel;
class ModerationActions final : public Singleton class ModerationActions final : public Singleton
{ {
public: public:
ModerationActions(); ModerationActions();
virtual void initialize(Settings &settings, Paths &paths) override; virtual void initialize(Settings &settings, Paths &paths) override;
UnsortedSignalVector<ModerationAction> items; UnsortedSignalVector<ModerationAction> items;
ModerationActionModel *createModel(QObject *parent); ModerationActionModel *createModel(QObject *parent);
private: private:
std::unique_ptr<ChatterinoSetting<std::vector<ModerationAction>>> setting_; std::unique_ptr<ChatterinoSetting<std::vector<ModerationAction>>> setting_;
bool initialized_ = false; bool initialized_ = false;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,36 +1,36 @@
#include "TaggedUser.hpp" #include "TaggedUser.hpp"
#include <tuple> #include <tuple>
namespace chatterino { namespace chatterino {
TaggedUser::TaggedUser(ProviderId provider, const QString &name, TaggedUser::TaggedUser(ProviderId provider, const QString &name,
const QString &id) const QString &id)
: providerId_(provider) : providerId_(provider)
, name_(name) , name_(name)
, id_(id) , id_(id)
{ {
} }
bool TaggedUser::operator<(const TaggedUser &other) const bool TaggedUser::operator<(const TaggedUser &other) const
{ {
return std::tie(this->providerId_, this->name_, this->id_) < return std::tie(this->providerId_, this->name_, this->id_) <
std::tie(other.providerId_, other.name_, other.id_); std::tie(other.providerId_, other.name_, other.id_);
} }
ProviderId TaggedUser::getProviderId() const ProviderId TaggedUser::getProviderId() const
{ {
return this->providerId_; return this->providerId_;
} }
QString TaggedUser::getName() const QString TaggedUser::getName() const
{ {
return this->name_; return this->name_;
} }
QString TaggedUser::getId() const QString TaggedUser::getId() const
{ {
return this->id_; return this->id_;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,26 +1,26 @@
#pragma once #pragma once
#include "common/ProviderId.hpp" #include "common/ProviderId.hpp"
#include <QString> #include <QString>
namespace chatterino { namespace chatterino {
class TaggedUser class TaggedUser
{ {
public: public:
TaggedUser(ProviderId providerId, const QString &name, const QString &id); TaggedUser(ProviderId providerId, const QString &name, const QString &id);
bool operator<(const TaggedUser &other) const; bool operator<(const TaggedUser &other) const;
ProviderId getProviderId() const; ProviderId getProviderId() const;
QString getName() const; QString getName() const;
QString getId() const; QString getId() const;
private: private:
ProviderId providerId_; ProviderId providerId_;
QString name_; QString name_;
QString id_; QString id_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,19 +1,19 @@
#include "TaggedUsersController.hpp" #include "TaggedUsersController.hpp"
#include "controllers/taggedusers/TaggedUsersModel.hpp" #include "controllers/taggedusers/TaggedUsersModel.hpp"
namespace chatterino { namespace chatterino {
TaggedUsersController::TaggedUsersController() TaggedUsersController::TaggedUsersController()
{ {
} }
TaggedUsersModel *TaggedUsersController::createModel(QObject *parent) TaggedUsersModel *TaggedUsersController::createModel(QObject *parent)
{ {
TaggedUsersModel *model = new TaggedUsersModel(parent); TaggedUsersModel *model = new TaggedUsersModel(parent);
model->init(&this->users); model->init(&this->users);
return model; return model;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,22 +1,22 @@
#pragma once #pragma once
#include "common/Singleton.hpp" #include "common/Singleton.hpp"
#include "common/SignalVector.hpp" #include "common/SignalVector.hpp"
#include "controllers/taggedusers/TaggedUser.hpp" #include "controllers/taggedusers/TaggedUser.hpp"
namespace chatterino { namespace chatterino {
class TaggedUsersModel; class TaggedUsersModel;
class TaggedUsersController final : public Singleton class TaggedUsersController final : public Singleton
{ {
public: public:
TaggedUsersController(); TaggedUsersController();
SortedSignalVector<TaggedUser, std::less<TaggedUser>> users; SortedSignalVector<TaggedUser, std::less<TaggedUser>> users;
TaggedUsersModel *createModel(QObject *parent = nullptr); TaggedUsersModel *createModel(QObject *parent = nullptr);
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,67 +1,67 @@
#include "TaggedUsersModel.hpp" #include "TaggedUsersModel.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "util/StandardItemHelper.hpp" #include "util/StandardItemHelper.hpp"
namespace chatterino { namespace chatterino {
// commandmodel // commandmodel
TaggedUsersModel::TaggedUsersModel(QObject *parent) TaggedUsersModel::TaggedUsersModel(QObject *parent)
: SignalVectorModel<TaggedUser>(1, parent) : SignalVectorModel<TaggedUser>(1, parent)
{ {
} }
// turn a vector item into a model row // turn a vector item into a model row
TaggedUser TaggedUsersModel::getItemFromRow(std::vector<QStandardItem *> &row, TaggedUser TaggedUsersModel::getItemFromRow(std::vector<QStandardItem *> &row,
const TaggedUser &original) const TaggedUser &original)
{ {
return original; return original;
} }
// turns a row in the model into a vector item // turns a row in the model into a vector item
void TaggedUsersModel::getRowFromItem(const TaggedUser &item, void TaggedUsersModel::getRowFromItem(const TaggedUser &item,
std::vector<QStandardItem *> &row) std::vector<QStandardItem *> &row)
{ {
setStringItem(row[0], item.getName()); setStringItem(row[0], item.getName());
} }
void TaggedUsersModel::afterInit() void TaggedUsersModel::afterInit()
{ {
// std::vector<QStandardItem *> row = this->createRow(); // std::vector<QStandardItem *> row = this->createRow();
// setBoolItem(row[0], // setBoolItem(row[0],
// getSettings()->enableHighlightsSelf.getValue(), true, false); // getSettings()->enableHighlightsSelf.getValue(), true, false);
// row[0]->setData("Your username (automatic)", Qt::DisplayRole); // row[0]->setData("Your username (automatic)", Qt::DisplayRole);
// setBoolItem(row[1], // setBoolItem(row[1],
// getSettings()->enableHighlightTaskbar.getValue(), true, false); // getSettings()->enableHighlightTaskbar.getValue(), true, false);
// setBoolItem(row[2], // setBoolItem(row[2],
// getSettings()->enableHighlightSound.getValue(), true, false); // getSettings()->enableHighlightSound.getValue(), true, false);
// row[3]->setFlags(0); this->insertCustomRow(row, 0); // row[3]->setFlags(0); this->insertCustomRow(row, 0);
} }
// void TaggedUserModel::customRowSetData(const std::vector<QStandardItem *> // void TaggedUserModel::customRowSetData(const std::vector<QStandardItem *>
// &row, int column, // &row, int column,
// const QVariant &value, int role) // const QVariant &value, int role)
//{ //{
// switch (column) { // switch (column) {
// case 0: { // case 0: {
// if (role == Qt::CheckStateRole) { // if (role == Qt::CheckStateRole) {
// getSettings()->enableHighlightsSelf.setValue(value.toBool()); // getSettings()->enableHighlightsSelf.setValue(value.toBool());
// } // }
// } break; // } break;
// case 1: { // case 1: {
// if (role == Qt::CheckStateRole) { // if (role == Qt::CheckStateRole) {
// getSettings()->enableHighlightTaskbar.setValue(value.toBool()); // getSettings()->enableHighlightTaskbar.setValue(value.toBool());
// } // }
// } break; // } break;
// case 2: { // case 2: {
// if (role == Qt::CheckStateRole) { // if (role == Qt::CheckStateRole) {
// getSettings()->enableHighlightSound.setValue(value.toBool()); // getSettings()->enableHighlightSound.setValue(value.toBool());
// } // }
// } break; // } break;
// case 3: { // case 3: {
// // empty element // // empty element
// } break; // } break;
// } // }
//} //}
} // namespace chatterino } // namespace chatterino

View file

@ -1,33 +1,33 @@
#pragma once #pragma once
#include "common/SignalVectorModel.hpp" #include "common/SignalVectorModel.hpp"
#include "controllers/taggedusers/TaggedUser.hpp" #include "controllers/taggedusers/TaggedUser.hpp"
namespace chatterino { namespace chatterino {
class TaggedUsersController; class TaggedUsersController;
class TaggedUsersModel : public SignalVectorModel<TaggedUser> class TaggedUsersModel : public SignalVectorModel<TaggedUser>
{ {
explicit TaggedUsersModel(QObject *parent); explicit TaggedUsersModel(QObject *parent);
protected: protected:
// turn a vector item into a model row // turn a vector item into a model row
virtual TaggedUser getItemFromRow(std::vector<QStandardItem *> &row, virtual TaggedUser getItemFromRow(std::vector<QStandardItem *> &row,
const TaggedUser &original) override; const TaggedUser &original) override;
// turns a row in the model into a vector item // turns a row in the model into a vector item
virtual void getRowFromItem(const TaggedUser &item, virtual void getRowFromItem(const TaggedUser &item,
std::vector<QStandardItem *> &row) override; std::vector<QStandardItem *> &row) override;
virtual void afterInit() override; virtual void afterInit() override;
// virtual void customRowSetData(const std::vector<QStandardItem *> &row, // virtual void customRowSetData(const std::vector<QStandardItem *> &row,
// int column, // int column,
// const QVariant &value, int role) // const QVariant &value, int role)
// override; // override;
friend class TaggedUsersController; friend class TaggedUsersController;
}; };
} // namespace chatterino } // namespace chatterino

View file

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

View file

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

View file

@ -1,90 +1,90 @@
#pragma once #pragma once
#include <tuple> #include <tuple>
#include <utility> #include <utility>
namespace chatterino { namespace chatterino {
struct SelectionItem { struct SelectionItem {
int messageIndex; int messageIndex;
int charIndex; int charIndex;
SelectionItem() SelectionItem()
{ {
this->messageIndex = 0; this->messageIndex = 0;
this->charIndex = 0; this->charIndex = 0;
} }
SelectionItem(int _messageIndex, int _charIndex) SelectionItem(int _messageIndex, int _charIndex)
{ {
this->messageIndex = _messageIndex; this->messageIndex = _messageIndex;
this->charIndex = _charIndex; this->charIndex = _charIndex;
} }
bool operator<(const SelectionItem &b) const bool operator<(const SelectionItem &b) const
{ {
return std::tie(this->messageIndex, this->charIndex) < return std::tie(this->messageIndex, this->charIndex) <
std::tie(b.messageIndex, b.charIndex); std::tie(b.messageIndex, b.charIndex);
} }
bool operator>(const SelectionItem &b) const bool operator>(const SelectionItem &b) const
{ {
return !this->operator==(b) && b.operator<(*this); return !this->operator==(b) && b.operator<(*this);
} }
bool operator==(const SelectionItem &b) const bool operator==(const SelectionItem &b) const
{ {
return this->messageIndex == b.messageIndex && return this->messageIndex == b.messageIndex &&
this->charIndex == b.charIndex; this->charIndex == b.charIndex;
} }
bool operator!=(const SelectionItem &b) const bool operator!=(const SelectionItem &b) const
{ {
return this->operator==(b); return this->operator==(b);
} }
}; };
struct Selection { struct Selection {
SelectionItem start; SelectionItem start;
SelectionItem end; SelectionItem end;
SelectionItem selectionMin; SelectionItem selectionMin;
SelectionItem selectionMax; SelectionItem selectionMax;
Selection() = default; Selection() = default;
Selection(const SelectionItem &start, const SelectionItem &end) Selection(const SelectionItem &start, const SelectionItem &end)
: start(start) : start(start)
, end(end) , end(end)
, selectionMin(start) , selectionMin(start)
, selectionMax(end) , selectionMax(end)
{ {
if (selectionMin > selectionMax) if (selectionMin > selectionMax)
{ {
std::swap(this->selectionMin, this->selectionMax); std::swap(this->selectionMin, this->selectionMax);
} }
} }
bool isEmpty() const bool isEmpty() const
{ {
return this->start == this->end; return this->start == this->end;
} }
bool isSingleMessage() const bool isSingleMessage() const
{ {
return this->selectionMin.messageIndex == return this->selectionMin.messageIndex ==
this->selectionMax.messageIndex; this->selectionMax.messageIndex;
} }
}; };
struct DoubleClickSelection { struct DoubleClickSelection {
int originalStart = 0; int originalStart = 0;
int originalEnd = 0; int originalEnd = 0;
int origMessageIndex; int origMessageIndex;
bool selectingLeft = false; bool selectingLeft = false;
bool selectingRight = false; bool selectingRight = false;
SelectionItem origStartItem; SelectionItem origStartItem;
SelectionItem origEndItem; SelectionItem origEndItem;
}; };
} // namespace chatterino } // namespace chatterino

File diff suppressed because it is too large Load diff

View file

@ -1,117 +1,117 @@
#pragma once #pragma once
#include <QPoint> #include <QPoint>
#include <QRect> #include <QRect>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "common/Common.hpp" #include "common/Common.hpp"
#include "common/FlagsEnum.hpp" #include "common/FlagsEnum.hpp"
#include "messages/Selection.hpp" #include "messages/Selection.hpp"
#include "messages/layouts/MessageLayoutElement.hpp" #include "messages/layouts/MessageLayoutElement.hpp"
class QPainter; class QPainter;
namespace chatterino { namespace chatterino {
enum class MessageFlag : uint32_t; enum class MessageFlag : uint32_t;
using MessageFlags = FlagsEnum<MessageFlag>; using MessageFlags = FlagsEnum<MessageFlag>;
struct Margin { struct Margin {
int top; int top;
int right; int right;
int bottom; int bottom;
int left; int left;
Margin() Margin()
: Margin(0) : Margin(0)
{ {
} }
Margin(int value) Margin(int value)
: Margin(value, value, value, value) : Margin(value, value, value, value)
{ {
} }
Margin(int _top, int _right, int _bottom, int _left) Margin(int _top, int _right, int _bottom, int _left)
: top(_top) : top(_top)
, right(_right) , right(_right)
, bottom(_bottom) , bottom(_bottom)
, left(_left) , left(_left)
{ {
} }
}; };
struct MessageLayoutContainer { struct MessageLayoutContainer {
MessageLayoutContainer() = default; MessageLayoutContainer() = default;
Margin margin = {4, 8, 4, 8}; Margin margin = {4, 8, 4, 8};
bool centered = false; bool centered = false;
bool enableCompactEmotes = false; bool enableCompactEmotes = false;
int getHeight() const; int getHeight() const;
int getWidth() const; int getWidth() const;
float getScale() const; float getScale() const;
// methods // methods
void begin(int width_, float scale_, MessageFlags flags_); void begin(int width_, float scale_, MessageFlags flags_);
void end(); void end();
void clear(); void clear();
bool canAddElements(); bool canAddElements();
void addElement(MessageLayoutElement *element); void addElement(MessageLayoutElement *element);
void addElementNoLineBreak(MessageLayoutElement *element); void addElementNoLineBreak(MessageLayoutElement *element);
void breakLine(); void breakLine();
bool atStartOfLine(); bool atStartOfLine();
bool fitsInLine(int width_); bool fitsInLine(int width_);
MessageLayoutElement *getElementAt(QPoint point); MessageLayoutElement *getElementAt(QPoint point);
// painting // painting
void paintElements(QPainter &painter); void paintElements(QPainter &painter);
void paintAnimatedElements(QPainter &painter, int yOffset); void paintAnimatedElements(QPainter &painter, int yOffset);
void paintSelection(QPainter &painter, int messageIndex, void paintSelection(QPainter &painter, int messageIndex,
Selection &selection, int yOffset); Selection &selection, int yOffset);
// selection // selection
int getSelectionIndex(QPoint point); int getSelectionIndex(QPoint point);
int getLastCharacterIndex() const; int getLastCharacterIndex() const;
int getFirstMessageCharacterIndex() const; int getFirstMessageCharacterIndex() const;
void addSelectionText(QString &str, int from, int to, CopyMode copymode); void addSelectionText(QString &str, int from, int to, CopyMode copymode);
bool isCollapsed(); bool isCollapsed();
private: private:
struct Line { struct Line {
int startIndex; int startIndex;
int endIndex; int endIndex;
int startCharIndex; int startCharIndex;
int endCharIndex; int endCharIndex;
QRect rect; QRect rect;
}; };
// helpers // helpers
void _addElement(MessageLayoutElement *element, bool forceAdd = false); void _addElement(MessageLayoutElement *element, bool forceAdd = false);
bool canCollapse(); bool canCollapse();
// variables // variables
float scale_ = 1.f; float scale_ = 1.f;
int width_ = 0; int width_ = 0;
MessageFlags flags_{}; MessageFlags flags_{};
int line_ = 0; int line_ = 0;
int height_ = 0; int height_ = 0;
int currentX_ = 0; int currentX_ = 0;
int currentY_ = 0; int currentY_ = 0;
int charIndex_ = 0; int charIndex_ = 0;
size_t lineStart_ = 0; size_t lineStart_ = 0;
int lineHeight_ = 0; int lineHeight_ = 0;
int spaceWidth_ = 4; int spaceWidth_ = 4;
int textLineHeight_ = 0; int textLineHeight_ = 0;
int dotdotdotWidth_ = 0; int dotdotdotWidth_ = 0;
bool canAddMessages_ = true; bool canAddMessages_ = true;
bool isCollapsed_ = false; bool isCollapsed_ = false;
std::vector<std::unique_ptr<MessageLayoutElement>> elements_; std::vector<std::unique_ptr<MessageLayoutElement>> elements_;
std::vector<Line> lines_; std::vector<Line> lines_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,59 +1,59 @@
#pragma once #pragma once
namespace chatterino { namespace chatterino {
template <typename T> template <typename T>
class NullablePtr class NullablePtr
{ {
public: public:
NullablePtr() NullablePtr()
: element_(nullptr) : element_(nullptr)
{ {
} }
NullablePtr(T *element) NullablePtr(T *element)
: element_(element) : element_(element)
{ {
} }
T *operator->() const T *operator->() const
{ {
assert(this->hasElement()); assert(this->hasElement());
return element_; return element_;
} }
T &operator*() const T &operator*() const
{ {
assert(this->hasElement()); assert(this->hasElement());
return *element_; return *element_;
} }
T *get() const T *get() const
{ {
assert(this->hasElement()); assert(this->hasElement());
return this->element_; return this->element_;
} }
bool isNull() const bool isNull() const
{ {
return this->element_ == nullptr; return this->element_ == nullptr;
} }
bool hasElement() const bool hasElement() const
{ {
return this->element_ != nullptr; return this->element_ != nullptr;
} }
operator bool() const operator bool() const
{ {
return this->hasElement(); return this->hasElement();
} }
private: private:
T *element_; T *element_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,350 +1,350 @@
#include "AbstractIrcServer.hpp" #include "AbstractIrcServer.hpp"
#include "common/Channel.hpp" #include "common/Channel.hpp"
#include "common/Common.hpp" #include "common/Common.hpp"
#include "debug/Log.hpp" #include "debug/Log.hpp"
#include "messages/LimitedQueueSnapshot.hpp" #include "messages/LimitedQueueSnapshot.hpp"
#include "messages/Message.hpp" #include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp" #include "messages/MessageBuilder.hpp"
#include <QCoreApplication> #include <QCoreApplication>
namespace chatterino { namespace chatterino {
const int RECONNECT_BASE_INTERVAL = 2000; const int RECONNECT_BASE_INTERVAL = 2000;
// 60 falloff counter means it will try to reconnect at most every 60*2 seconds // 60 falloff counter means it will try to reconnect at most every 60*2 seconds
const int MAX_FALLOFF_COUNTER = 60; const int MAX_FALLOFF_COUNTER = 60;
AbstractIrcServer::AbstractIrcServer() AbstractIrcServer::AbstractIrcServer()
{ {
// Initialize the connections // Initialize the connections
this->writeConnection_.reset(new IrcConnection); this->writeConnection_.reset(new IrcConnection);
this->writeConnection_->moveToThread( this->writeConnection_->moveToThread(
QCoreApplication::instance()->thread()); QCoreApplication::instance()->thread());
QObject::connect( QObject::connect(
this->writeConnection_.get(), &Communi::IrcConnection::messageReceived, this->writeConnection_.get(), &Communi::IrcConnection::messageReceived,
[this](auto msg) { this->writeConnectionMessageReceived(msg); }); [this](auto msg) { this->writeConnectionMessageReceived(msg); });
// Listen to read connection message signals // Listen to read connection message signals
this->readConnection_.reset(new IrcConnection); this->readConnection_.reset(new IrcConnection);
this->readConnection_->moveToThread(QCoreApplication::instance()->thread()); this->readConnection_->moveToThread(QCoreApplication::instance()->thread());
QObject::connect( QObject::connect(
this->readConnection_.get(), &Communi::IrcConnection::messageReceived, this->readConnection_.get(), &Communi::IrcConnection::messageReceived,
[this](auto msg) { this->readConnectionMessageReceived(msg); }); [this](auto msg) { this->readConnectionMessageReceived(msg); });
QObject::connect(this->readConnection_.get(), QObject::connect(this->readConnection_.get(),
&Communi::IrcConnection::privateMessageReceived, &Communi::IrcConnection::privateMessageReceived,
[this](auto msg) { this->privateMessageReceived(msg); }); [this](auto msg) { this->privateMessageReceived(msg); });
QObject::connect( QObject::connect(
this->readConnection_.get(), &Communi::IrcConnection::connected, this->readConnection_.get(), &Communi::IrcConnection::connected,
[this] { this->onReadConnected(this->readConnection_.get()); }); [this] { this->onReadConnected(this->readConnection_.get()); });
QObject::connect( QObject::connect(
this->writeConnection_.get(), &Communi::IrcConnection::connected, this->writeConnection_.get(), &Communi::IrcConnection::connected,
[this] { this->onWriteConnected(this->writeConnection_.get()); }); [this] { this->onWriteConnected(this->writeConnection_.get()); });
QObject::connect(this->readConnection_.get(), QObject::connect(this->readConnection_.get(),
&Communi::IrcConnection::disconnected, &Communi::IrcConnection::disconnected,
[this] { this->onDisconnected(); }); [this] { this->onDisconnected(); });
QObject::connect(this->readConnection_.get(), QObject::connect(this->readConnection_.get(),
&Communi::IrcConnection::socketError, &Communi::IrcConnection::socketError,
[this] { this->onSocketError(); }); [this] { this->onSocketError(); });
// listen to reconnect request // listen to reconnect request
this->readConnection_->reconnectRequested.connect( this->readConnection_->reconnectRequested.connect(
[this] { this->connect(); }); [this] { this->connect(); });
// this->writeConnection->reconnectRequested.connect([this] { // this->writeConnection->reconnectRequested.connect([this] {
// this->connect(); }); // this->connect(); });
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL); this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL);
this->reconnectTimer_.setSingleShot(true); this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] { QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL * this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL *
this->falloffCounter_); this->falloffCounter_);
this->falloffCounter_ = this->falloffCounter_ =
std::min(MAX_FALLOFF_COUNTER, this->falloffCounter_ + 1); std::min(MAX_FALLOFF_COUNTER, this->falloffCounter_ + 1);
if (!this->readConnection_->isConnected()) if (!this->readConnection_->isConnected())
{ {
log("Trying to reconnect... {}", this->falloffCounter_); log("Trying to reconnect... {}", this->falloffCounter_);
this->connect(); this->connect();
} }
}); });
} }
void AbstractIrcServer::connect() void AbstractIrcServer::connect()
{ {
this->disconnect(); this->disconnect();
bool separateWriteConnection = this->hasSeparateWriteConnection(); bool separateWriteConnection = this->hasSeparateWriteConnection();
if (separateWriteConnection) if (separateWriteConnection)
{ {
this->initializeConnection(this->writeConnection_.get(), false, true); this->initializeConnection(this->writeConnection_.get(), false, true);
this->initializeConnection(this->readConnection_.get(), true, false); this->initializeConnection(this->readConnection_.get(), true, false);
} }
else else
{ {
this->initializeConnection(this->readConnection_.get(), true, true); this->initializeConnection(this->readConnection_.get(), true, true);
} }
// fourtf: this should be asynchronous // fourtf: this should be asynchronous
{ {
std::lock_guard<std::mutex> lock1(this->connectionMutex_); std::lock_guard<std::mutex> lock1(this->connectionMutex_);
std::lock_guard<std::mutex> lock2(this->channelMutex); std::lock_guard<std::mutex> lock2(this->channelMutex);
for (std::weak_ptr<Channel> &weak : this->channels.values()) for (std::weak_ptr<Channel> &weak : this->channels.values())
{ {
if (auto channel = std::shared_ptr<Channel>(weak.lock())) if (auto channel = std::shared_ptr<Channel>(weak.lock()))
{ {
this->readConnection_->sendRaw("JOIN #" + channel->getName()); this->readConnection_->sendRaw("JOIN #" + channel->getName());
} }
} }
this->writeConnection_->open(); this->writeConnection_->open();
this->readConnection_->open(); this->readConnection_->open();
} }
// this->onConnected(); // this->onConnected();
// possbile event: started to connect // possbile event: started to connect
} }
void AbstractIrcServer::disconnect() void AbstractIrcServer::disconnect()
{ {
std::lock_guard<std::mutex> locker(this->connectionMutex_); std::lock_guard<std::mutex> locker(this->connectionMutex_);
this->readConnection_->close(); this->readConnection_->close();
this->writeConnection_->close(); this->writeConnection_->close();
} }
void AbstractIrcServer::sendMessage(const QString &channelName, void AbstractIrcServer::sendMessage(const QString &channelName,
const QString &message) const QString &message)
{ {
this->sendRawMessage("PRIVMSG #" + channelName + " :" + message); this->sendRawMessage("PRIVMSG #" + channelName + " :" + message);
} }
void AbstractIrcServer::sendRawMessage(const QString &rawMessage) void AbstractIrcServer::sendRawMessage(const QString &rawMessage)
{ {
std::lock_guard<std::mutex> locker(this->connectionMutex_); std::lock_guard<std::mutex> locker(this->connectionMutex_);
if (this->hasSeparateWriteConnection()) if (this->hasSeparateWriteConnection())
{ {
this->writeConnection_->sendRaw(rawMessage); this->writeConnection_->sendRaw(rawMessage);
} }
else else
{ {
this->readConnection_->sendRaw(rawMessage); this->readConnection_->sendRaw(rawMessage);
} }
} }
void AbstractIrcServer::writeConnectionMessageReceived( void AbstractIrcServer::writeConnectionMessageReceived(
Communi::IrcMessage *message) Communi::IrcMessage *message)
{ {
} }
std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel( std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
const QString &dirtyChannelName) const QString &dirtyChannelName)
{ {
auto channelName = this->cleanChannelName(dirtyChannelName); auto channelName = this->cleanChannelName(dirtyChannelName);
// try get channel // try get channel
ChannelPtr chan = this->getChannelOrEmpty(channelName); ChannelPtr chan = this->getChannelOrEmpty(channelName);
if (chan != Channel::getEmpty()) if (chan != Channel::getEmpty())
{ {
return chan; return chan;
} }
std::lock_guard<std::mutex> lock(this->channelMutex); std::lock_guard<std::mutex> lock(this->channelMutex);
// value doesn't exist // value doesn't exist
chan = this->createChannel(channelName); chan = this->createChannel(channelName);
if (!chan) if (!chan)
{ {
return Channel::getEmpty(); return Channel::getEmpty();
} }
QString clojuresInCppAreShit = channelName; QString clojuresInCppAreShit = channelName;
this->channels.insert(channelName, chan); this->channels.insert(channelName, chan);
chan->destroyed.connect([this, clojuresInCppAreShit] { chan->destroyed.connect([this, clojuresInCppAreShit] {
// fourtf: issues when the server itself is destroyed // fourtf: issues when the server itself is destroyed
log("[AbstractIrcServer::addChannel] {} was destroyed", log("[AbstractIrcServer::addChannel] {} was destroyed",
clojuresInCppAreShit); clojuresInCppAreShit);
this->channels.remove(clojuresInCppAreShit); this->channels.remove(clojuresInCppAreShit);
if (this->readConnection_) if (this->readConnection_)
{ {
this->readConnection_->sendRaw("PART #" + clojuresInCppAreShit); this->readConnection_->sendRaw("PART #" + clojuresInCppAreShit);
} }
if (this->writeConnection_) if (this->writeConnection_)
{ {
this->writeConnection_->sendRaw("PART #" + clojuresInCppAreShit); this->writeConnection_->sendRaw("PART #" + clojuresInCppAreShit);
} }
}); });
// join irc channel // join irc channel
{ {
std::lock_guard<std::mutex> lock2(this->connectionMutex_); std::lock_guard<std::mutex> lock2(this->connectionMutex_);
if (this->readConnection_) if (this->readConnection_)
{ {
this->readConnection_->sendRaw("JOIN #" + channelName); this->readConnection_->sendRaw("JOIN #" + channelName);
} }
if (this->writeConnection_) if (this->writeConnection_)
{ {
this->writeConnection_->sendRaw("JOIN #" + channelName); this->writeConnection_->sendRaw("JOIN #" + channelName);
} }
} }
return chan; return chan;
} }
std::shared_ptr<Channel> AbstractIrcServer::getChannelOrEmpty( std::shared_ptr<Channel> AbstractIrcServer::getChannelOrEmpty(
const QString &dirtyChannelName) const QString &dirtyChannelName)
{ {
auto channelName = this->cleanChannelName(dirtyChannelName); auto channelName = this->cleanChannelName(dirtyChannelName);
std::lock_guard<std::mutex> lock(this->channelMutex); std::lock_guard<std::mutex> lock(this->channelMutex);
// try get special channel // try get special channel
ChannelPtr chan = this->getCustomChannel(channelName); ChannelPtr chan = this->getCustomChannel(channelName);
if (chan) if (chan)
{ {
return chan; return chan;
} }
// value exists // value exists
auto it = this->channels.find(channelName); auto it = this->channels.find(channelName);
if (it != this->channels.end()) if (it != this->channels.end())
{ {
chan = it.value().lock(); chan = it.value().lock();
if (chan) if (chan)
{ {
return chan; return chan;
} }
} }
return Channel::getEmpty(); return Channel::getEmpty();
} }
void AbstractIrcServer::onReadConnected(IrcConnection *connection) void AbstractIrcServer::onReadConnected(IrcConnection *connection)
{ {
std::lock_guard<std::mutex> lock(this->channelMutex); std::lock_guard<std::mutex> lock(this->channelMutex);
auto connectedMsg = makeSystemMessage("connected"); auto connectedMsg = makeSystemMessage("connected");
connectedMsg->flags.set(MessageFlag::ConnectedMessage); connectedMsg->flags.set(MessageFlag::ConnectedMessage);
auto reconnected = makeSystemMessage("reconnected"); auto reconnected = makeSystemMessage("reconnected");
reconnected->flags.set(MessageFlag::ConnectedMessage); reconnected->flags.set(MessageFlag::ConnectedMessage);
for (std::weak_ptr<Channel> &weak : this->channels.values()) for (std::weak_ptr<Channel> &weak : this->channels.values())
{ {
std::shared_ptr<Channel> chan = weak.lock(); std::shared_ptr<Channel> chan = weak.lock();
if (!chan) if (!chan)
{ {
continue; continue;
} }
LimitedQueueSnapshot<MessagePtr> snapshot = chan->getMessageSnapshot(); LimitedQueueSnapshot<MessagePtr> snapshot = chan->getMessageSnapshot();
bool replaceMessage = bool replaceMessage =
snapshot.size() > 0 && snapshot[snapshot.size() - 1]->flags.has( snapshot.size() > 0 && snapshot[snapshot.size() - 1]->flags.has(
MessageFlag::DisconnectedMessage); MessageFlag::DisconnectedMessage);
if (replaceMessage) if (replaceMessage)
{ {
chan->replaceMessage(snapshot[snapshot.size() - 1], reconnected); chan->replaceMessage(snapshot[snapshot.size() - 1], reconnected);
continue; continue;
} }
chan->addMessage(connectedMsg); chan->addMessage(connectedMsg);
} }
this->falloffCounter_ = 1; this->falloffCounter_ = 1;
} }
void AbstractIrcServer::onWriteConnected(IrcConnection *connection) void AbstractIrcServer::onWriteConnected(IrcConnection *connection)
{ {
} }
void AbstractIrcServer::onDisconnected() void AbstractIrcServer::onDisconnected()
{ {
std::lock_guard<std::mutex> lock(this->channelMutex); std::lock_guard<std::mutex> lock(this->channelMutex);
MessageBuilder b(systemMessage, "disconnected"); MessageBuilder b(systemMessage, "disconnected");
b->flags.set(MessageFlag::DisconnectedMessage); b->flags.set(MessageFlag::DisconnectedMessage);
auto disconnectedMsg = b.release(); auto disconnectedMsg = b.release();
for (std::weak_ptr<Channel> &weak : this->channels.values()) for (std::weak_ptr<Channel> &weak : this->channels.values())
{ {
std::shared_ptr<Channel> chan = weak.lock(); std::shared_ptr<Channel> chan = weak.lock();
if (!chan) if (!chan)
{ {
continue; continue;
} }
chan->addMessage(disconnectedMsg); chan->addMessage(disconnectedMsg);
} }
} }
void AbstractIrcServer::onSocketError() void AbstractIrcServer::onSocketError()
{ {
this->reconnectTimer_.start(); this->reconnectTimer_.start();
} }
std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel( std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel(
const QString &channelName) const QString &channelName)
{ {
return nullptr; return nullptr;
} }
QString AbstractIrcServer::cleanChannelName(const QString &dirtyChannelName) QString AbstractIrcServer::cleanChannelName(const QString &dirtyChannelName)
{ {
return dirtyChannelName; return dirtyChannelName;
} }
void AbstractIrcServer::addFakeMessage(const QString &data) void AbstractIrcServer::addFakeMessage(const QString &data)
{ {
auto fakeMessage = Communi::IrcMessage::fromData( auto fakeMessage = Communi::IrcMessage::fromData(
data.toUtf8(), this->readConnection_.get()); data.toUtf8(), this->readConnection_.get());
if (fakeMessage->command() == "PRIVMSG") if (fakeMessage->command() == "PRIVMSG")
{ {
this->privateMessageReceived( this->privateMessageReceived(
static_cast<Communi::IrcPrivateMessage *>(fakeMessage)); static_cast<Communi::IrcPrivateMessage *>(fakeMessage));
} }
else else
{ {
this->readConnectionMessageReceived(fakeMessage); this->readConnectionMessageReceived(fakeMessage);
} }
} }
void AbstractIrcServer::privateMessageReceived( void AbstractIrcServer::privateMessageReceived(
Communi::IrcPrivateMessage *message) Communi::IrcPrivateMessage *message)
{ {
} }
void AbstractIrcServer::readConnectionMessageReceived( void AbstractIrcServer::readConnectionMessageReceived(
Communi::IrcMessage *message) Communi::IrcMessage *message)
{ {
} }
void AbstractIrcServer::forEachChannel(std::function<void(ChannelPtr)> func) void AbstractIrcServer::forEachChannel(std::function<void(ChannelPtr)> func)
{ {
std::lock_guard<std::mutex> lock(this->channelMutex); std::lock_guard<std::mutex> lock(this->channelMutex);
for (std::weak_ptr<Channel> &weak : this->channels.values()) for (std::weak_ptr<Channel> &weak : this->channels.values())
{ {
std::shared_ptr<Channel> chan = weak.lock(); std::shared_ptr<Channel> chan = weak.lock();
if (!chan) if (!chan)
{ {
continue; continue;
} }
func(chan); func(chan);
} }
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,83 +1,83 @@
#pragma once #pragma once
#include "providers/irc/IrcConnection2.hpp" #include "providers/irc/IrcConnection2.hpp"
#include <IrcMessage> #include <IrcMessage>
#include <pajlada/signals/signal.hpp> #include <pajlada/signals/signal.hpp>
#include <functional> #include <functional>
#include <mutex> #include <mutex>
namespace chatterino { namespace chatterino {
class Channel; class Channel;
using ChannelPtr = std::shared_ptr<Channel>; using ChannelPtr = std::shared_ptr<Channel>;
class AbstractIrcServer class AbstractIrcServer
{ {
public: public:
virtual ~AbstractIrcServer() = default; virtual ~AbstractIrcServer() = default;
// connection // connection
void connect(); void connect();
void disconnect(); void disconnect();
void sendMessage(const QString &channelName, const QString &message); void sendMessage(const QString &channelName, const QString &message);
void sendRawMessage(const QString &rawMessage); void sendRawMessage(const QString &rawMessage);
// channels // channels
std::shared_ptr<Channel> getOrAddChannel(const QString &dirtyChannelName); std::shared_ptr<Channel> getOrAddChannel(const QString &dirtyChannelName);
std::shared_ptr<Channel> getChannelOrEmpty(const QString &dirtyChannelName); std::shared_ptr<Channel> getChannelOrEmpty(const QString &dirtyChannelName);
// signals // signals
pajlada::Signals::NoArgSignal connected; pajlada::Signals::NoArgSignal connected;
pajlada::Signals::NoArgSignal disconnected; pajlada::Signals::NoArgSignal disconnected;
// pajlada::Signals::Signal<Communi::IrcPrivateMessage *> // pajlada::Signals::Signal<Communi::IrcPrivateMessage *>
// onPrivateMessage; // onPrivateMessage;
void addFakeMessage(const QString &data); void addFakeMessage(const QString &data);
// iteration // iteration
void forEachChannel(std::function<void(ChannelPtr)> func); void forEachChannel(std::function<void(ChannelPtr)> func);
protected: protected:
AbstractIrcServer(); AbstractIrcServer();
virtual void initializeConnection(IrcConnection *connection, bool isRead, virtual void initializeConnection(IrcConnection *connection, bool isRead,
bool isWrite) = 0; bool isWrite) = 0;
virtual std::shared_ptr<Channel> createChannel( virtual std::shared_ptr<Channel> createChannel(
const QString &channelName) = 0; const QString &channelName) = 0;
virtual void privateMessageReceived(Communi::IrcPrivateMessage *message); virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
virtual void readConnectionMessageReceived(Communi::IrcMessage *message); virtual void readConnectionMessageReceived(Communi::IrcMessage *message);
virtual void writeConnectionMessageReceived(Communi::IrcMessage *message); virtual void writeConnectionMessageReceived(Communi::IrcMessage *message);
virtual void onReadConnected(IrcConnection *connection); virtual void onReadConnected(IrcConnection *connection);
virtual void onWriteConnected(IrcConnection *connection); virtual void onWriteConnected(IrcConnection *connection);
virtual void onDisconnected(); virtual void onDisconnected();
virtual void onSocketError(); virtual void onSocketError();
virtual std::shared_ptr<Channel> getCustomChannel( virtual std::shared_ptr<Channel> getCustomChannel(
const QString &channelName); const QString &channelName);
virtual bool hasSeparateWriteConnection() const = 0; virtual bool hasSeparateWriteConnection() const = 0;
virtual QString cleanChannelName(const QString &dirtyChannelName); virtual QString cleanChannelName(const QString &dirtyChannelName);
QMap<QString, std::weak_ptr<Channel>> channels; QMap<QString, std::weak_ptr<Channel>> channels;
std::mutex channelMutex; std::mutex channelMutex;
private: private:
void initConnection(); void initConnection();
std::unique_ptr<IrcConnection> writeConnection_ = nullptr; std::unique_ptr<IrcConnection> writeConnection_ = nullptr;
std::unique_ptr<IrcConnection> readConnection_ = nullptr; std::unique_ptr<IrcConnection> readConnection_ = nullptr;
QTimer reconnectTimer_; QTimer reconnectTimer_;
int falloffCounter_ = 1; int falloffCounter_ = 1;
std::mutex connectionMutex_; std::mutex connectionMutex_;
// bool autoReconnect_ = false; // bool autoReconnect_ = false;
}; };
} // namespace chatterino } // namespace chatterino

View file

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

View file

@ -1,11 +1,11 @@
#pragma once #pragma once
namespace chatterino { namespace chatterino {
// class IrcChannel // class IrcChannel
//{ //{
// public: // public:
// IrcChannel(); // IrcChannel();
//}; //};
// //
} // namespace chatterino } // namespace chatterino

View file

@ -1,44 +1,44 @@
#include "IrcConnection2.hpp" #include "IrcConnection2.hpp"
namespace chatterino { namespace chatterino {
IrcConnection::IrcConnection(QObject *parent) IrcConnection::IrcConnection(QObject *parent)
: Communi::IrcConnection(parent) : Communi::IrcConnection(parent)
{ {
// send ping every x seconds // send ping every x seconds
this->pingTimer_.setInterval(5000); this->pingTimer_.setInterval(5000);
this->pingTimer_.start(); this->pingTimer_.start();
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] { QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
if (this->isConnected()) if (this->isConnected())
{ {
if (!this->recentlyReceivedMessage_.load()) if (!this->recentlyReceivedMessage_.load())
{ {
this->sendRaw("PING"); this->sendRaw("PING");
this->reconnectTimer_.start(); this->reconnectTimer_.start();
} }
this->recentlyReceivedMessage_ = false; this->recentlyReceivedMessage_ = false;
} }
}); });
// reconnect after x seconds without receiving a message // reconnect after x seconds without receiving a message
this->reconnectTimer_.setInterval(5000); this->reconnectTimer_.setInterval(5000);
this->reconnectTimer_.setSingleShot(true); this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] { QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
if (this->isConnected()) if (this->isConnected())
{ {
reconnectRequested.invoke(); reconnectRequested.invoke();
} }
}); });
QObject::connect(this, &Communi::IrcConnection::messageReceived, QObject::connect(this, &Communi::IrcConnection::messageReceived,
[this](Communi::IrcMessage *) { [this](Communi::IrcMessage *) {
this->recentlyReceivedMessage_ = true; this->recentlyReceivedMessage_ = true;
if (this->reconnectTimer_.isActive()) if (this->reconnectTimer_.isActive())
{ {
this->reconnectTimer_.stop(); this->reconnectTimer_.stop();
} }
}); });
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,23 +1,23 @@
#pragma once #pragma once
#include <pajlada/signals/signal.hpp> #include <pajlada/signals/signal.hpp>
#include <IrcConnection> #include <IrcConnection>
#include <QTimer> #include <QTimer>
namespace chatterino { namespace chatterino {
class IrcConnection : public Communi::IrcConnection class IrcConnection : public Communi::IrcConnection
{ {
public: public:
IrcConnection(QObject *parent = nullptr); IrcConnection(QObject *parent = nullptr);
pajlada::Signals::NoArgSignal reconnectRequested; pajlada::Signals::NoArgSignal reconnectRequested;
private: private:
QTimer pingTimer_; QTimer pingTimer_;
QTimer reconnectTimer_; QTimer reconnectTimer_;
std::atomic<bool> recentlyReceivedMessage_{true}; std::atomic<bool> recentlyReceivedMessage_{true};
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,12 +1,12 @@
#include "IrcServer.hpp" #include "IrcServer.hpp"
#include <cassert> #include <cassert>
namespace chatterino { namespace chatterino {
// IrcServer::IrcServer(const QString &hostname, int port) // IrcServer::IrcServer(const QString &hostname, int port)
//{ //{
// this->initConnection(); // this->initConnection();
//} //}
// //
} // namespace chatterino } // namespace chatterino

View file

@ -1,24 +1,24 @@
#pragma once #pragma once
#include "providers/irc/AbstractIrcServer.hpp" #include "providers/irc/AbstractIrcServer.hpp"
#include "providers/irc/IrcAccount.hpp" #include "providers/irc/IrcAccount.hpp"
namespace chatterino { namespace chatterino {
// class IrcServer // class IrcServer
//{ //{
// public: // public:
// IrcServer(const QString &hostname, int port); // IrcServer(const QString &hostname, int port);
// void setAccount(std::shared_ptr<IrcAccount> newAccount); // void setAccount(std::shared_ptr<IrcAccount> newAccount);
// std::shared_ptr<IrcAccount> getAccount() const; // std::shared_ptr<IrcAccount> getAccount() const;
// protected: // protected:
// virtual void initializeConnection(Communi::IrcConnection *connection, bool // virtual void initializeConnection(Communi::IrcConnection *connection, bool
// isReadConnection); // isReadConnection);
// virtual void privateMessageReceived(Communi::IrcPrivateMessage *message); // virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
// virtual void messageReceived(Communi::IrcMessage *message); // virtual void messageReceived(Communi::IrcMessage *message);
//}; //};
// //
} // namespace chatterino } // namespace chatterino

File diff suppressed because it is too large Load diff

View file

@ -1,58 +1,58 @@
#pragma once #pragma once
#include <IrcMessage> #include <IrcMessage>
#include "messages/Message.hpp" #include "messages/Message.hpp"
namespace chatterino { namespace chatterino {
class TwitchServer; class TwitchServer;
class Channel; class Channel;
class IrcMessageHandler class IrcMessageHandler
{ {
IrcMessageHandler() = default; IrcMessageHandler() = default;
public: public:
static IrcMessageHandler &getInstance(); static IrcMessageHandler &getInstance();
// parseMessage parses a single IRC message into 0+ Chatterino messages // parseMessage parses a single IRC message into 0+ Chatterino messages
std::vector<MessagePtr> parseMessage(Channel *channel, std::vector<MessagePtr> parseMessage(Channel *channel,
Communi::IrcMessage *message); Communi::IrcMessage *message);
// parsePrivMessage arses a single IRC PRIVMSG into 0-1 Chatterino messages // parsePrivMessage arses a single IRC PRIVMSG into 0-1 Chatterino messages
std::vector<MessagePtr> parsePrivMessage( std::vector<MessagePtr> parsePrivMessage(
Channel *channel, Communi::IrcPrivateMessage *message); Channel *channel, Communi::IrcPrivateMessage *message);
void handlePrivMessage(Communi::IrcPrivateMessage *message, void handlePrivMessage(Communi::IrcPrivateMessage *message,
TwitchServer &server); TwitchServer &server);
void handleRoomStateMessage(Communi::IrcMessage *message); void handleRoomStateMessage(Communi::IrcMessage *message);
void handleClearChatMessage(Communi::IrcMessage *message); void handleClearChatMessage(Communi::IrcMessage *message);
void handleClearMessageMessage(Communi::IrcMessage *message); void handleClearMessageMessage(Communi::IrcMessage *message);
void handleUserStateMessage(Communi::IrcMessage *message); void handleUserStateMessage(Communi::IrcMessage *message);
void handleWhisperMessage(Communi::IrcMessage *message); void handleWhisperMessage(Communi::IrcMessage *message);
// parseUserNoticeMessage parses a single IRC USERNOTICE message into 0+ // parseUserNoticeMessage parses a single IRC USERNOTICE message into 0+
// chatterino messages // chatterino messages
std::vector<MessagePtr> parseUserNoticeMessage( std::vector<MessagePtr> parseUserNoticeMessage(
Channel *channel, Communi::IrcMessage *message); Channel *channel, Communi::IrcMessage *message);
void handleUserNoticeMessage(Communi::IrcMessage *message, void handleUserNoticeMessage(Communi::IrcMessage *message,
TwitchServer &server); TwitchServer &server);
void handleModeMessage(Communi::IrcMessage *message); void handleModeMessage(Communi::IrcMessage *message);
// parseNoticeMessage parses a single IRC NOTICE message into 0+ chatterino // parseNoticeMessage parses a single IRC NOTICE message into 0+ chatterino
// messages // messages
std::vector<MessagePtr> parseNoticeMessage( std::vector<MessagePtr> parseNoticeMessage(
Communi::IrcNoticeMessage *message); Communi::IrcNoticeMessage *message);
void handleNoticeMessage(Communi::IrcNoticeMessage *message); void handleNoticeMessage(Communi::IrcNoticeMessage *message);
void handleJoinMessage(Communi::IrcMessage *message); void handleJoinMessage(Communi::IrcMessage *message);
void handlePartMessage(Communi::IrcMessage *message); void handlePartMessage(Communi::IrcMessage *message);
private: private:
void addMessage(Communi::IrcMessage *message, const QString &target, void addMessage(Communi::IrcMessage *message, const QString &target,
const QString &content, TwitchServer &server, bool isResub, const QString &content, TwitchServer &server, bool isResub,
bool isAction); bool isAction);
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,234 +1,234 @@
#include "providers/twitch/TwitchAccountManager.hpp" #include "providers/twitch/TwitchAccountManager.hpp"
#include "common/Common.hpp" #include "common/Common.hpp"
#include "debug/Log.hpp" #include "debug/Log.hpp"
#include "providers/twitch/TwitchAccount.hpp" #include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchCommon.hpp" #include "providers/twitch/TwitchCommon.hpp"
namespace chatterino { namespace chatterino {
TwitchAccountManager::TwitchAccountManager() TwitchAccountManager::TwitchAccountManager()
: anonymousUser_(new TwitchAccount(ANONYMOUS_USERNAME, "", "", "")) : anonymousUser_(new TwitchAccount(ANONYMOUS_USERNAME, "", "", ""))
{ {
this->currentUserChanged.connect([this] { this->currentUserChanged.connect([this] {
auto currentUser = this->getCurrent(); auto currentUser = this->getCurrent();
currentUser->loadIgnores(); currentUser->loadIgnores();
}); });
this->accounts.itemRemoved.connect([this](const auto &acc) { // this->accounts.itemRemoved.connect([this](const auto &acc) { //
this->removeUser(acc.item.get()); this->removeUser(acc.item.get());
}); });
} }
std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent() std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent()
{ {
if (!this->currentUser_) if (!this->currentUser_)
{ {
return this->anonymousUser_; return this->anonymousUser_;
} }
return this->currentUser_; return this->currentUser_;
} }
std::vector<QString> TwitchAccountManager::getUsernames() const std::vector<QString> TwitchAccountManager::getUsernames() const
{ {
std::vector<QString> userNames; std::vector<QString> userNames;
std::lock_guard<std::mutex> lock(this->mutex_); std::lock_guard<std::mutex> lock(this->mutex_);
for (const auto &user : this->accounts) for (const auto &user : this->accounts)
{ {
userNames.push_back(user->getUserName()); userNames.push_back(user->getUserName());
} }
return userNames; return userNames;
} }
std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername( std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
const QString &username) const const QString &username) const
{ {
std::lock_guard<std::mutex> lock(this->mutex_); std::lock_guard<std::mutex> lock(this->mutex_);
for (const auto &user : this->accounts) for (const auto &user : this->accounts)
{ {
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0) if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0)
{ {
return user; return user;
} }
} }
return nullptr; return nullptr;
} }
bool TwitchAccountManager::userExists(const QString &username) const bool TwitchAccountManager::userExists(const QString &username) const
{ {
return this->findUserByUsername(username) != nullptr; return this->findUserByUsername(username) != nullptr;
} }
void TwitchAccountManager::reloadUsers() void TwitchAccountManager::reloadUsers()
{ {
auto keys = pajlada::Settings::SettingManager::getObjectKeys("/accounts"); auto keys = pajlada::Settings::SettingManager::getObjectKeys("/accounts");
UserData userData; UserData userData;
bool listUpdated = false; bool listUpdated = false;
for (const auto &uid : keys) for (const auto &uid : keys)
{ {
if (uid == "current") if (uid == "current")
{ {
continue; continue;
} }
auto username = pajlada::Settings::Setting<QString>::get( auto username = pajlada::Settings::Setting<QString>::get(
"/accounts/" + uid + "/username"); "/accounts/" + uid + "/username");
auto userID = pajlada::Settings::Setting<QString>::get("/accounts/" + auto userID = pajlada::Settings::Setting<QString>::get("/accounts/" +
uid + "/userID"); uid + "/userID");
auto clientID = pajlada::Settings::Setting<QString>::get( auto clientID = pajlada::Settings::Setting<QString>::get(
"/accounts/" + uid + "/clientID"); "/accounts/" + uid + "/clientID");
auto oauthToken = pajlada::Settings::Setting<QString>::get( auto oauthToken = pajlada::Settings::Setting<QString>::get(
"/accounts/" + uid + "/oauthToken"); "/accounts/" + uid + "/oauthToken");
if (username.isEmpty() || userID.isEmpty() || clientID.isEmpty() || if (username.isEmpty() || userID.isEmpty() || clientID.isEmpty() ||
oauthToken.isEmpty()) oauthToken.isEmpty())
{ {
continue; continue;
} }
userData.username = username.trimmed(); userData.username = username.trimmed();
userData.userID = userID.trimmed(); userData.userID = userID.trimmed();
userData.clientID = clientID.trimmed(); userData.clientID = clientID.trimmed();
userData.oauthToken = oauthToken.trimmed(); userData.oauthToken = oauthToken.trimmed();
switch (this->addUser(userData)) switch (this->addUser(userData))
{ {
case AddUserResponse::UserAlreadyExists: case AddUserResponse::UserAlreadyExists:
{ {
log("User {} already exists", userData.username); log("User {} already exists", userData.username);
// Do nothing // Do nothing
} }
break; break;
case AddUserResponse::UserValuesUpdated: case AddUserResponse::UserValuesUpdated:
{ {
log("User {} already exists, and values updated!", log("User {} already exists, and values updated!",
userData.username); userData.username);
if (userData.username == this->getCurrent()->getUserName()) if (userData.username == this->getCurrent()->getUserName())
{ {
log("It was the current user, so we need to reconnect " log("It was the current user, so we need to reconnect "
"stuff!"); "stuff!");
this->currentUserChanged.invoke(); this->currentUserChanged.invoke();
} }
} }
break; break;
case AddUserResponse::UserAdded: case AddUserResponse::UserAdded:
{ {
log("Added user {}", userData.username); log("Added user {}", userData.username);
listUpdated = true; listUpdated = true;
} }
break; break;
} }
} }
if (listUpdated) if (listUpdated)
{ {
this->userListUpdated.invoke(); this->userListUpdated.invoke();
} }
} }
void TwitchAccountManager::load() void TwitchAccountManager::load()
{ {
this->reloadUsers(); this->reloadUsers();
this->currentUsername.connect([this](const QString &newUsername) { this->currentUsername.connect([this](const QString &newUsername) {
auto user = this->findUserByUsername(newUsername); auto user = this->findUserByUsername(newUsername);
if (user) if (user)
{ {
log("[AccountManager:currentUsernameChanged] User successfully " log("[AccountManager:currentUsernameChanged] User successfully "
"updated to {}", "updated to {}",
newUsername); newUsername);
this->currentUser_ = user; this->currentUser_ = user;
} }
else else
{ {
log("[AccountManager:currentUsernameChanged] User successfully " log("[AccountManager:currentUsernameChanged] User successfully "
"updated to anonymous"); "updated to anonymous");
this->currentUser_ = this->anonymousUser_; this->currentUser_ = this->anonymousUser_;
} }
this->currentUserChanged.invoke(); this->currentUserChanged.invoke();
}); });
} }
bool TwitchAccountManager::isLoggedIn() const bool TwitchAccountManager::isLoggedIn() const
{ {
if (!this->currentUser_) if (!this->currentUser_)
{ {
return false; return false;
} }
// Once `TwitchAccount` class has a way to check, we should also return // Once `TwitchAccount` class has a way to check, we should also return
// false if the credentials are incorrect // false if the credentials are incorrect
return !this->currentUser_->isAnon(); return !this->currentUser_->isAnon();
} }
bool TwitchAccountManager::removeUser(TwitchAccount *account) bool TwitchAccountManager::removeUser(TwitchAccount *account)
{ {
auto userID(account->getUserId()); auto userID(account->getUserId());
if (!userID.isEmpty()) if (!userID.isEmpty())
{ {
pajlada::Settings::SettingManager::removeSetting( pajlada::Settings::SettingManager::removeSetting(
fS("/accounts/uid{}", userID)); fS("/accounts/uid{}", userID));
} }
if (account->getUserName() == this->currentUsername) if (account->getUserName() == this->currentUsername)
{ {
// The user that was removed is the current user, log into the anonymous // The user that was removed is the current user, log into the anonymous
// user // user
this->currentUsername = ""; this->currentUsername = "";
} }
this->userListUpdated.invoke(); this->userListUpdated.invoke();
return true; return true;
} }
TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser( TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
const TwitchAccountManager::UserData &userData) const TwitchAccountManager::UserData &userData)
{ {
auto previousUser = this->findUserByUsername(userData.username); auto previousUser = this->findUserByUsername(userData.username);
if (previousUser) if (previousUser)
{ {
bool userUpdated = false; bool userUpdated = false;
if (previousUser->setOAuthClient(userData.clientID)) if (previousUser->setOAuthClient(userData.clientID))
{ {
userUpdated = true; userUpdated = true;
} }
if (previousUser->setOAuthToken(userData.oauthToken)) if (previousUser->setOAuthToken(userData.oauthToken))
{ {
userUpdated = true; userUpdated = true;
} }
if (userUpdated) if (userUpdated)
{ {
return AddUserResponse::UserValuesUpdated; return AddUserResponse::UserValuesUpdated;
} }
else else
{ {
return AddUserResponse::UserAlreadyExists; return AddUserResponse::UserAlreadyExists;
} }
} }
auto newUser = auto newUser =
std::make_shared<TwitchAccount>(userData.username, userData.oauthToken, std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
userData.clientID, userData.userID); userData.clientID, userData.userID);
// std::lock_guard<std::mutex> lock(this->mutex); // std::lock_guard<std::mutex> lock(this->mutex);
this->accounts.insertItem(newUser); this->accounts.insertItem(newUser);
return AddUserResponse::UserAdded; return AddUserResponse::UserAdded;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,75 +1,75 @@
#pragma once #pragma once
#include "common/ChatterinoSetting.hpp" #include "common/ChatterinoSetting.hpp"
#include "common/SignalVector.hpp" #include "common/SignalVector.hpp"
#include "providers/twitch/TwitchAccount.hpp" #include "providers/twitch/TwitchAccount.hpp"
#include "util/SharedPtrElementLess.hpp" #include "util/SharedPtrElementLess.hpp"
#include <mutex> #include <mutex>
#include <vector> #include <vector>
// //
// Warning: This class is not supposed to be created directly. // Warning: This class is not supposed to be created directly.
// Get yourself an instance from our friends over at // Get yourself an instance from our friends over at
// AccountManager.hpp // AccountManager.hpp
// //
namespace chatterino { namespace chatterino {
class TwitchAccount; class TwitchAccount;
class AccountController; class AccountController;
class TwitchAccountManager class TwitchAccountManager
{ {
TwitchAccountManager(); TwitchAccountManager();
public: public:
struct UserData { struct UserData {
QString username; QString username;
QString userID; QString userID;
QString clientID; QString clientID;
QString oauthToken; QString oauthToken;
}; };
// Returns the current twitchUsers, or the anonymous user if we're not // Returns the current twitchUsers, or the anonymous user if we're not
// currently logged in // currently logged in
std::shared_ptr<TwitchAccount> getCurrent(); std::shared_ptr<TwitchAccount> getCurrent();
std::vector<QString> getUsernames() const; std::vector<QString> getUsernames() const;
std::shared_ptr<TwitchAccount> findUserByUsername( std::shared_ptr<TwitchAccount> findUserByUsername(
const QString &username) const; const QString &username) const;
bool userExists(const QString &username) const; bool userExists(const QString &username) const;
void reloadUsers(); void reloadUsers();
void load(); void load();
bool isLoggedIn() const; bool isLoggedIn() const;
pajlada::Settings::Setting<QString> currentUsername{"/accounts/current", pajlada::Settings::Setting<QString> currentUsername{"/accounts/current",
""}; ""};
pajlada::Signals::NoArgSignal currentUserChanged; pajlada::Signals::NoArgSignal currentUserChanged;
pajlada::Signals::NoArgSignal userListUpdated; pajlada::Signals::NoArgSignal userListUpdated;
SortedSignalVector<std::shared_ptr<TwitchAccount>, SortedSignalVector<std::shared_ptr<TwitchAccount>,
SharedPtrElementLess<TwitchAccount>> SharedPtrElementLess<TwitchAccount>>
accounts; accounts;
private: private:
enum class AddUserResponse { enum class AddUserResponse {
UserAlreadyExists, UserAlreadyExists,
UserValuesUpdated, UserValuesUpdated,
UserAdded, UserAdded,
}; };
AddUserResponse addUser(const UserData &data); AddUserResponse addUser(const UserData &data);
bool removeUser(TwitchAccount *account); bool removeUser(TwitchAccount *account);
std::shared_ptr<TwitchAccount> currentUser_; std::shared_ptr<TwitchAccount> currentUser_;
std::shared_ptr<TwitchAccount> anonymousUser_; std::shared_ptr<TwitchAccount> anonymousUser_;
mutable std::mutex mutex_; mutable std::mutex mutex_;
friend class AccountController; friend class AccountController;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,366 +1,366 @@
#include "TwitchServer.hpp" #include "TwitchServer.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "common/Common.hpp" #include "common/Common.hpp"
#include "controllers/accounts/AccountController.hpp" #include "controllers/accounts/AccountController.hpp"
#include "controllers/highlights/HighlightController.hpp" #include "controllers/highlights/HighlightController.hpp"
#include "messages/Message.hpp" #include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp" #include "messages/MessageBuilder.hpp"
#include "providers/twitch/ChatroomChannel.hpp" #include "providers/twitch/ChatroomChannel.hpp"
#include "providers/twitch/IrcMessageHandler.hpp" #include "providers/twitch/IrcMessageHandler.hpp"
#include "providers/twitch/PubsubClient.hpp" #include "providers/twitch/PubsubClient.hpp"
#include "providers/twitch/TwitchAccount.hpp" #include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchHelpers.hpp" #include "providers/twitch/TwitchHelpers.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp" #include "providers/twitch/TwitchMessageBuilder.hpp"
#include "util/PostToThread.hpp" #include "util/PostToThread.hpp"
#include <IrcCommand> #include <IrcCommand>
#include <cassert> #include <cassert>
// using namespace Communi; // using namespace Communi;
using namespace std::chrono_literals; using namespace std::chrono_literals;
namespace chatterino { namespace chatterino {
namespace { namespace {
bool isChatroom(const QString &channel) bool isChatroom(const QString &channel)
{ {
if (channel.left(10) == "chatrooms:") if (channel.left(10) == "chatrooms:")
{ {
auto reflist = channel.splitRef(':'); auto reflist = channel.splitRef(':');
if (reflist.size() == 3) if (reflist.size() == 3)
{ {
return true; return true;
} }
} }
return false; return false;
} }
} // namespace } // namespace
TwitchServer::TwitchServer() TwitchServer::TwitchServer()
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers)) : whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions)) , mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching) , watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
{ {
qDebug() << "init TwitchServer"; qDebug() << "init TwitchServer";
this->pubsub = new PubSub; this->pubsub = new PubSub;
// getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) { // getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) {
// this->connect(); }, // this->connect(); },
// this->signalHolder_, // this->signalHolder_,
// false); // false);
} }
void TwitchServer::initialize(Settings &settings, Paths &paths) void TwitchServer::initialize(Settings &settings, Paths &paths)
{ {
getApp()->accounts->twitch.currentUserChanged.connect( getApp()->accounts->twitch.currentUserChanged.connect(
[this]() { postToThread([this] { this->connect(); }); }); [this]() { postToThread([this] { this->connect(); }); });
this->twitchBadges.loadTwitchBadges(); this->twitchBadges.loadTwitchBadges();
this->bttv.loadEmotes(); this->bttv.loadEmotes();
this->ffz.loadEmotes(); this->ffz.loadEmotes();
} }
void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead, void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
bool isWrite) bool isWrite)
{ {
this->singleConnection_ = isRead == isWrite; this->singleConnection_ = isRead == isWrite;
std::shared_ptr<TwitchAccount> account = std::shared_ptr<TwitchAccount> account =
getApp()->accounts->twitch.getCurrent(); getApp()->accounts->twitch.getCurrent();
qDebug() << "logging in as" << account->getUserName(); qDebug() << "logging in as" << account->getUserName();
QString username = account->getUserName(); QString username = account->getUserName();
QString oauthToken = account->getOAuthToken(); QString oauthToken = account->getOAuthToken();
if (!oauthToken.startsWith("oauth:")) if (!oauthToken.startsWith("oauth:"))
{ {
oauthToken.prepend("oauth:"); oauthToken.prepend("oauth:");
} }
connection->setUserName(username); connection->setUserName(username);
connection->setNickName(username); connection->setNickName(username);
connection->setRealName(username); connection->setRealName(username);
if (!account->isAnon()) if (!account->isAnon())
{ {
connection->setPassword(oauthToken); connection->setPassword(oauthToken);
} }
connection->setSecure(true); connection->setSecure(true);
// https://dev.twitch.tv/docs/irc/guide/#connecting-to-twitch-irc // https://dev.twitch.tv/docs/irc/guide/#connecting-to-twitch-irc
// SSL disabled: irc://irc.chat.twitch.tv:6667 // SSL disabled: irc://irc.chat.twitch.tv:6667
// SSL enabled: irc://irc.chat.twitch.tv:6697 // SSL enabled: irc://irc.chat.twitch.tv:6697
connection->setHost("irc.chat.twitch.tv"); connection->setHost("irc.chat.twitch.tv");
connection->setPort(6697); connection->setPort(6697);
} }
std::shared_ptr<Channel> TwitchServer::createChannel(const QString &channelName) std::shared_ptr<Channel> TwitchServer::createChannel(const QString &channelName)
{ {
std::shared_ptr<TwitchChannel> channel; std::shared_ptr<TwitchChannel> channel;
if (isChatroom(channelName)) if (isChatroom(channelName))
{ {
channel = std::static_pointer_cast<TwitchChannel>( channel = std::static_pointer_cast<TwitchChannel>(
std::shared_ptr<ChatroomChannel>(new ChatroomChannel( std::shared_ptr<ChatroomChannel>(new ChatroomChannel(
channelName, this->twitchBadges, this->bttv, this->ffz))); channelName, this->twitchBadges, this->bttv, this->ffz)));
} }
else else
{ {
channel = std::shared_ptr<TwitchChannel>(new TwitchChannel( channel = std::shared_ptr<TwitchChannel>(new TwitchChannel(
channelName, this->twitchBadges, this->bttv, this->ffz)); channelName, this->twitchBadges, this->bttv, this->ffz));
} }
channel->initialize(); channel->initialize();
channel->sendMessageSignal.connect( channel->sendMessageSignal.connect(
[this, channel = channel.get()](auto &chan, auto &msg, bool &sent) { [this, channel = channel.get()](auto &chan, auto &msg, bool &sent) {
this->onMessageSendRequested(channel, msg, sent); this->onMessageSendRequested(channel, msg, sent);
}); });
return std::shared_ptr<Channel>(channel); return std::shared_ptr<Channel>(channel);
} }
void TwitchServer::privateMessageReceived(Communi::IrcPrivateMessage *message) void TwitchServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
{ {
IrcMessageHandler::getInstance().handlePrivMessage(message, *this); IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
} }
void TwitchServer::readConnectionMessageReceived(Communi::IrcMessage *message) void TwitchServer::readConnectionMessageReceived(Communi::IrcMessage *message)
{ {
if (message->type() == Communi::IrcMessage::Type::Private) if (message->type() == Communi::IrcMessage::Type::Private)
{ {
// We already have a handler for private messages // We already have a handler for private messages
return; return;
} }
const QString &command = message->command(); const QString &command = message->command();
auto &handler = IrcMessageHandler::getInstance(); auto &handler = IrcMessageHandler::getInstance();
// Below commands enabled through the twitch.tv/membership CAP REQ // Below commands enabled through the twitch.tv/membership CAP REQ
if (command == "MODE") if (command == "MODE")
{ {
handler.handleModeMessage(message); handler.handleModeMessage(message);
} }
else if (command == "JOIN") else if (command == "JOIN")
{ {
handler.handleJoinMessage(message); handler.handleJoinMessage(message);
} }
else if (command == "PART") else if (command == "PART")
{ {
handler.handlePartMessage(message); handler.handlePartMessage(message);
} }
} }
void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message) void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
{ {
const QString &command = message->command(); const QString &command = message->command();
auto &handler = IrcMessageHandler::getInstance(); auto &handler = IrcMessageHandler::getInstance();
// Below commands enabled through the twitch.tv/commands CAP REQ // Below commands enabled through the twitch.tv/commands CAP REQ
if (command == "USERSTATE") if (command == "USERSTATE")
{ {
handler.handleUserStateMessage(message); handler.handleUserStateMessage(message);
} }
else if (command == "WHISPER") else if (command == "WHISPER")
{ {
handler.handleWhisperMessage(message); handler.handleWhisperMessage(message);
} }
else if (command == "USERNOTICE") else if (command == "USERNOTICE")
{ {
handler.handleUserNoticeMessage(message, *this); handler.handleUserNoticeMessage(message, *this);
} }
else if (command == "ROOMSTATE") else if (command == "ROOMSTATE")
{ {
handler.handleRoomStateMessage(message); handler.handleRoomStateMessage(message);
} }
else if (command == "CLEARCHAT") else if (command == "CLEARCHAT")
{ {
handler.handleClearChatMessage(message); handler.handleClearChatMessage(message);
} }
else if (command == "CLEARMSG") else if (command == "CLEARMSG")
{ {
handler.handleClearMessageMessage(message); handler.handleClearMessageMessage(message);
} }
else if (command == "NOTICE") else if (command == "NOTICE")
{ {
handler.handleNoticeMessage( handler.handleNoticeMessage(
static_cast<Communi::IrcNoticeMessage *>(message)); static_cast<Communi::IrcNoticeMessage *>(message));
} }
} }
void TwitchServer::onReadConnected(IrcConnection *connection) void TwitchServer::onReadConnected(IrcConnection *connection)
{ {
AbstractIrcServer::onReadConnected(connection); AbstractIrcServer::onReadConnected(connection);
// twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/ // twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/
// twitch.tv/membership enables the JOIN/PART/MODE/NAMES commands. See https://dev.twitch.tv/docs/irc/membership/ // twitch.tv/membership enables the JOIN/PART/MODE/NAMES commands. See https://dev.twitch.tv/docs/irc/membership/
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/membership"); connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/membership");
} }
void TwitchServer::onWriteConnected(IrcConnection *connection) void TwitchServer::onWriteConnected(IrcConnection *connection)
{ {
AbstractIrcServer::onWriteConnected(connection); AbstractIrcServer::onWriteConnected(connection);
// twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/ // twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/
// twitch.tv/commands enables a bunch of miscellaneous command capabilities. See https://dev.twitch.tv/docs/irc/commands/ // twitch.tv/commands enables a bunch of miscellaneous command capabilities. See https://dev.twitch.tv/docs/irc/commands/
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/commands"); connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/commands");
} }
std::shared_ptr<Channel> TwitchServer::getCustomChannel( std::shared_ptr<Channel> TwitchServer::getCustomChannel(
const QString &channelName) const QString &channelName)
{ {
if (channelName == "/whispers") if (channelName == "/whispers")
{ {
return this->whispersChannel; return this->whispersChannel;
} }
if (channelName == "/mentions") if (channelName == "/mentions")
{ {
return this->mentionsChannel; return this->mentionsChannel;
} }
if (channelName == "$$$") if (channelName == "$$$")
{ {
static auto channel = static auto channel =
std::make_shared<Channel>("$$$", chatterino::Channel::Type::Misc); std::make_shared<Channel>("$$$", chatterino::Channel::Type::Misc);
static auto getTimer = [&] { static auto getTimer = [&] {
for (auto i = 0; i < 1000; i++) for (auto i = 0; i < 1000; i++)
{ {
channel->addMessage(makeSystemMessage(QString::number(i + 1))); channel->addMessage(makeSystemMessage(QString::number(i + 1)));
} }
auto timer = new QTimer; auto timer = new QTimer;
QObject::connect(timer, &QTimer::timeout, [] { QObject::connect(timer, &QTimer::timeout, [] {
channel->addMessage( channel->addMessage(
makeSystemMessage(QTime::currentTime().toString())); makeSystemMessage(QTime::currentTime().toString()));
}); });
timer->start(500); timer->start(500);
return timer; return timer;
}(); }();
return channel; return channel;
} }
return nullptr; return nullptr;
} }
void TwitchServer::forEachChannelAndSpecialChannels( void TwitchServer::forEachChannelAndSpecialChannels(
std::function<void(ChannelPtr)> func) std::function<void(ChannelPtr)> func)
{ {
this->forEachChannel(func); this->forEachChannel(func);
func(this->whispersChannel); func(this->whispersChannel);
func(this->mentionsChannel); func(this->mentionsChannel);
} }
std::shared_ptr<Channel> TwitchServer::getChannelOrEmptyByID( std::shared_ptr<Channel> TwitchServer::getChannelOrEmptyByID(
const QString &channelId) const QString &channelId)
{ {
std::lock_guard<std::mutex> lock(this->channelMutex); std::lock_guard<std::mutex> lock(this->channelMutex);
for (const auto &weakChannel : this->channels) for (const auto &weakChannel : this->channels)
{ {
auto channel = weakChannel.lock(); auto channel = weakChannel.lock();
if (!channel) if (!channel)
continue; continue;
auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(channel); auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(channel);
if (!twitchChannel) if (!twitchChannel)
continue; continue;
if (twitchChannel->roomId() == channelId && if (twitchChannel->roomId() == channelId &&
twitchChannel->getName().splitRef(":").size() < 3) twitchChannel->getName().splitRef(":").size() < 3)
{ {
return twitchChannel; return twitchChannel;
} }
} }
return Channel::getEmpty(); return Channel::getEmpty();
} }
QString TwitchServer::cleanChannelName(const QString &dirtyChannelName) QString TwitchServer::cleanChannelName(const QString &dirtyChannelName)
{ {
return dirtyChannelName.toLower(); return dirtyChannelName.toLower();
} }
bool TwitchServer::hasSeparateWriteConnection() const bool TwitchServer::hasSeparateWriteConnection() const
{ {
return true; return true;
// return getSettings()->twitchSeperateWriteConnection; // return getSettings()->twitchSeperateWriteConnection;
} }
void TwitchServer::onMessageSendRequested(TwitchChannel *channel, void TwitchServer::onMessageSendRequested(TwitchChannel *channel,
const QString &message, bool &sent) const QString &message, bool &sent)
{ {
sent = false; sent = false;
{ {
std::lock_guard<std::mutex> guard(this->lastMessageMutex_); std::lock_guard<std::mutex> guard(this->lastMessageMutex_);
// std::queue<std::chrono::steady_clock::time_point> // std::queue<std::chrono::steady_clock::time_point>
auto &lastMessage = channel->hasHighRateLimit() auto &lastMessage = channel->hasHighRateLimit()
? this->lastMessageMod_ ? this->lastMessageMod_
: this->lastMessagePleb_; : this->lastMessagePleb_;
size_t maxMessageCount = channel->hasHighRateLimit() ? 99 : 19; size_t maxMessageCount = channel->hasHighRateLimit() ? 99 : 19;
auto minMessageOffset = (channel->hasHighRateLimit() ? 100ms : 1100ms); auto minMessageOffset = (channel->hasHighRateLimit() ? 100ms : 1100ms);
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();
// check if you are sending messages too fast // check if you are sending messages too fast
if (!lastMessage.empty() && lastMessage.back() + minMessageOffset > now) if (!lastMessage.empty() && lastMessage.back() + minMessageOffset > now)
{ {
if (this->lastErrorTimeSpeed_ + 30s < now) if (this->lastErrorTimeSpeed_ + 30s < now)
{ {
auto errorMessage = auto errorMessage =
makeSystemMessage("sending messages too fast"); makeSystemMessage("sending messages too fast");
channel->addMessage(errorMessage); channel->addMessage(errorMessage);
this->lastErrorTimeSpeed_ = now; this->lastErrorTimeSpeed_ = now;
} }
return; return;
} }
// remove messages older than 30 seconds // remove messages older than 30 seconds
while (!lastMessage.empty() && lastMessage.front() + 32s < now) while (!lastMessage.empty() && lastMessage.front() + 32s < now)
{ {
lastMessage.pop(); lastMessage.pop();
} }
// check if you are sending too many messages // check if you are sending too many messages
if (lastMessage.size() >= maxMessageCount) if (lastMessage.size() >= maxMessageCount)
{ {
if (this->lastErrorTimeAmount_ + 30s < now) if (this->lastErrorTimeAmount_ + 30s < now)
{ {
auto errorMessage = auto errorMessage =
makeSystemMessage("sending too many messages"); makeSystemMessage("sending too many messages");
channel->addMessage(errorMessage); channel->addMessage(errorMessage);
this->lastErrorTimeAmount_ = now; this->lastErrorTimeAmount_ = now;
} }
return; return;
} }
lastMessage.push(now); lastMessage.push(now);
} }
this->sendMessage(channel->getName(), message); this->sendMessage(channel->getName(), message);
sent = true; sent = true;
} }
const BttvEmotes &TwitchServer::getBttvEmotes() const const BttvEmotes &TwitchServer::getBttvEmotes() const
{ {
return this->bttv; return this->bttv;
} }
const FfzEmotes &TwitchServer::getFfzEmotes() const const FfzEmotes &TwitchServer::getFfzEmotes() const
{ {
return this->ffz; return this->ffz;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,86 +1,86 @@
#pragma once #pragma once
#include "common/Atomic.hpp" #include "common/Atomic.hpp"
#include "common/Channel.hpp" #include "common/Channel.hpp"
#include "common/Singleton.hpp" #include "common/Singleton.hpp"
#include "pajlada/signals/signalholder.hpp" #include "pajlada/signals/signalholder.hpp"
#include "providers/bttv/BttvEmotes.hpp" #include "providers/bttv/BttvEmotes.hpp"
#include "providers/ffz/FfzEmotes.hpp" #include "providers/ffz/FfzEmotes.hpp"
#include "providers/irc/AbstractIrcServer.hpp" #include "providers/irc/AbstractIrcServer.hpp"
#include "providers/twitch/TwitchBadges.hpp" #include "providers/twitch/TwitchBadges.hpp"
#include <chrono> #include <chrono>
#include <memory> #include <memory>
#include <queue> #include <queue>
namespace chatterino { namespace chatterino {
class Settings; class Settings;
class Paths; class Paths;
class PubSub; class PubSub;
class TwitchChannel; class TwitchChannel;
class TwitchServer final : public AbstractIrcServer, public Singleton class TwitchServer final : public AbstractIrcServer, public Singleton
{ {
public: public:
TwitchServer(); TwitchServer();
virtual ~TwitchServer() override = default; virtual ~TwitchServer() override = default;
virtual void initialize(Settings &settings, Paths &paths) override; virtual void initialize(Settings &settings, Paths &paths) override;
void forEachChannelAndSpecialChannels(std::function<void(ChannelPtr)> func); void forEachChannelAndSpecialChannels(std::function<void(ChannelPtr)> func);
std::shared_ptr<Channel> getChannelOrEmptyByID(const QString &channelID); std::shared_ptr<Channel> getChannelOrEmptyByID(const QString &channelID);
Atomic<QString> lastUserThatWhisperedMe; Atomic<QString> lastUserThatWhisperedMe;
const ChannelPtr whispersChannel; const ChannelPtr whispersChannel;
const ChannelPtr mentionsChannel; const ChannelPtr mentionsChannel;
IndirectChannel watchingChannel; IndirectChannel watchingChannel;
PubSub *pubsub; PubSub *pubsub;
const BttvEmotes &getBttvEmotes() const; const BttvEmotes &getBttvEmotes() const;
const FfzEmotes &getFfzEmotes() const; const FfzEmotes &getFfzEmotes() const;
protected: protected:
virtual void initializeConnection(IrcConnection *connection, bool isRead, virtual void initializeConnection(IrcConnection *connection, bool isRead,
bool isWrite) override; bool isWrite) override;
virtual std::shared_ptr<Channel> createChannel( virtual std::shared_ptr<Channel> createChannel(
const QString &channelName) override; const QString &channelName) override;
virtual void privateMessageReceived( virtual void privateMessageReceived(
Communi::IrcPrivateMessage *message) override; Communi::IrcPrivateMessage *message) override;
virtual void readConnectionMessageReceived( virtual void readConnectionMessageReceived(
Communi::IrcMessage *message) override; Communi::IrcMessage *message) override;
virtual void writeConnectionMessageReceived( virtual void writeConnectionMessageReceived(
Communi::IrcMessage *message) override; Communi::IrcMessage *message) override;
virtual void onReadConnected(IrcConnection *connection) override; virtual void onReadConnected(IrcConnection *connection) override;
virtual void onWriteConnected(IrcConnection *connection) override; virtual void onWriteConnected(IrcConnection *connection) override;
virtual std::shared_ptr<Channel> getCustomChannel( virtual std::shared_ptr<Channel> getCustomChannel(
const QString &channelname) override; const QString &channelname) override;
virtual QString cleanChannelName(const QString &dirtyChannelName) override; virtual QString cleanChannelName(const QString &dirtyChannelName) override;
virtual bool hasSeparateWriteConnection() const override; virtual bool hasSeparateWriteConnection() const override;
private: private:
void onMessageSendRequested(TwitchChannel *channel, const QString &message, void onMessageSendRequested(TwitchChannel *channel, const QString &message,
bool &sent); bool &sent);
std::mutex lastMessageMutex_; std::mutex lastMessageMutex_;
std::queue<std::chrono::steady_clock::time_point> lastMessagePleb_; std::queue<std::chrono::steady_clock::time_point> lastMessagePleb_;
std::queue<std::chrono::steady_clock::time_point> lastMessageMod_; std::queue<std::chrono::steady_clock::time_point> lastMessageMod_;
std::chrono::steady_clock::time_point lastErrorTimeSpeed_; std::chrono::steady_clock::time_point lastErrorTimeSpeed_;
std::chrono::steady_clock::time_point lastErrorTimeAmount_; std::chrono::steady_clock::time_point lastErrorTimeAmount_;
bool singleConnection_ = false; bool singleConnection_ = false;
TwitchBadges twitchBadges; TwitchBadges twitchBadges;
BttvEmotes bttv; BttvEmotes bttv;
FfzEmotes ffz; FfzEmotes ffz;
pajlada::Signals::SignalHolder signalHolder_; pajlada::Signals::SignalHolder signalHolder_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,272 +1,272 @@
#include "singletons/NativeMessaging.hpp" #include "singletons/NativeMessaging.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "providers/twitch/TwitchServer.hpp" #include "providers/twitch/TwitchServer.hpp"
#include "singletons/Paths.hpp" #include "singletons/Paths.hpp"
#include "util/PostToThread.hpp" #include "util/PostToThread.hpp"
#include <QCoreApplication> #include <QCoreApplication>
#include <QFile> #include <QFile>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonValue> #include <QJsonValue>
#include <boost/date_time/posix_time/posix_time.hpp> #include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/interprocess/ipc/message_queue.hpp> #include <boost/interprocess/ipc/message_queue.hpp>
namespace ipc = boost::interprocess; namespace ipc = boost::interprocess;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
# include <QProcess> # include <QProcess>
# include <Windows.h> # include <Windows.h>
# include "singletons/WindowManager.hpp" # include "singletons/WindowManager.hpp"
# include "widgets/AttachedWindow.hpp" # include "widgets/AttachedWindow.hpp"
#endif #endif
#include <iostream> #include <iostream>
#define EXTENSION_ID "glknmaideaikkmemifbfkhnomoknepka" #define EXTENSION_ID "glknmaideaikkmemifbfkhnomoknepka"
#define MESSAGE_SIZE 1024 #define MESSAGE_SIZE 1024
namespace chatterino { namespace chatterino {
void registerNmManifest(Paths &paths, const QString &manifestFilename, void registerNmManifest(Paths &paths, const QString &manifestFilename,
const QString &registryKeyName, const QString &registryKeyName,
const QJsonDocument &document); const QJsonDocument &document);
void registerNmHost(Paths &paths) void registerNmHost(Paths &paths)
{ {
if (paths.isPortable()) if (paths.isPortable())
return; return;
auto getBaseDocument = [&] { auto getBaseDocument = [&] {
QJsonObject obj; QJsonObject obj;
obj.insert("name", "com.chatterino.chatterino"); obj.insert("name", "com.chatterino.chatterino");
obj.insert("description", "Browser interaction with chatterino."); obj.insert("description", "Browser interaction with chatterino.");
obj.insert("path", QCoreApplication::applicationFilePath()); obj.insert("path", QCoreApplication::applicationFilePath());
obj.insert("type", "stdio"); obj.insert("type", "stdio");
return obj; return obj;
}; };
// chrome // chrome
{ {
QJsonDocument document; QJsonDocument document;
auto obj = getBaseDocument(); auto obj = getBaseDocument();
QJsonArray allowed_origins_arr = {"chrome-extension://" EXTENSION_ID QJsonArray allowed_origins_arr = {"chrome-extension://" EXTENSION_ID
"/"}; "/"};
obj.insert("allowed_origins", allowed_origins_arr); obj.insert("allowed_origins", allowed_origins_arr);
document.setObject(obj); document.setObject(obj);
registerNmManifest(paths, "/native-messaging-manifest-chrome.json", registerNmManifest(paths, "/native-messaging-manifest-chrome.json",
"HKCU\\Software\\Google\\Chrome\\NativeMessagingHost" "HKCU\\Software\\Google\\Chrome\\NativeMessagingHost"
"s\\com.chatterino.chatterino", "s\\com.chatterino.chatterino",
document); document);
} }
// firefox // firefox
{ {
QJsonDocument document; QJsonDocument document;
auto obj = getBaseDocument(); auto obj = getBaseDocument();
QJsonArray allowed_extensions = {"chatterino_native@chatterino.com"}; QJsonArray allowed_extensions = {"chatterino_native@chatterino.com"};
obj.insert("allowed_extensions", allowed_extensions); obj.insert("allowed_extensions", allowed_extensions);
document.setObject(obj); document.setObject(obj);
registerNmManifest(paths, "/native-messaging-manifest-firefox.json", registerNmManifest(paths, "/native-messaging-manifest-firefox.json",
"HKCU\\Software\\Mozilla\\NativeMessagingHosts\\com." "HKCU\\Software\\Mozilla\\NativeMessagingHosts\\com."
"chatterino.chatterino", "chatterino.chatterino",
document); document);
} }
} }
void registerNmManifest(Paths &paths, const QString &manifestFilename, void registerNmManifest(Paths &paths, const QString &manifestFilename,
const QString &registryKeyName, const QString &registryKeyName,
const QJsonDocument &document) const QJsonDocument &document)
{ {
(void)registryKeyName; (void)registryKeyName;
// save the manifest // save the manifest
QString manifestPath = paths.miscDirectory + manifestFilename; QString manifestPath = paths.miscDirectory + manifestFilename;
QFile file(manifestPath); QFile file(manifestPath);
file.open(QIODevice::WriteOnly | QIODevice::Truncate); file.open(QIODevice::WriteOnly | QIODevice::Truncate);
file.write(document.toJson()); file.write(document.toJson());
file.flush(); file.flush();
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
// clang-format off // clang-format off
QProcess::execute("REG ADD \"" + registryKeyName + "\" /ve /t REG_SZ /d \"" + manifestPath + "\" /f"); QProcess::execute("REG ADD \"" + registryKeyName + "\" /ve /t REG_SZ /d \"" + manifestPath + "\" /f");
// clang-format on // clang-format on
#endif #endif
} }
std::string &getNmQueueName(Paths &paths) std::string &getNmQueueName(Paths &paths)
{ {
static std::string name = static std::string name =
"chatterino_gui" + paths.applicationFilePathHash.toStdString(); "chatterino_gui" + paths.applicationFilePathHash.toStdString();
return name; return name;
} }
// CLIENT // CLIENT
void NativeMessagingClient::sendMessage(const QByteArray &array) void NativeMessagingClient::sendMessage(const QByteArray &array)
{ {
try try
{ {
ipc::message_queue messageQueue(ipc::open_only, "chatterino_gui"); ipc::message_queue messageQueue(ipc::open_only, "chatterino_gui");
messageQueue.try_send(array.data(), size_t(array.size()), 1); messageQueue.try_send(array.data(), size_t(array.size()), 1);
// messageQueue.timed_send(array.data(), size_t(array.size()), 1, // messageQueue.timed_send(array.data(), size_t(array.size()), 1,
// boost::posix_time::second_clock::local_time() + // boost::posix_time::second_clock::local_time() +
// boost::posix_time::seconds(10)); // boost::posix_time::seconds(10));
} }
catch (ipc::interprocess_exception &ex) catch (ipc::interprocess_exception &ex)
{ {
qDebug() << "send to gui process:" << ex.what(); qDebug() << "send to gui process:" << ex.what();
} }
} }
void NativeMessagingClient::writeToCout(const QByteArray &array) void NativeMessagingClient::writeToCout(const QByteArray &array)
{ {
auto *data = array.data(); auto *data = array.data();
auto size = uint32_t(array.size()); auto size = uint32_t(array.size());
std::cout.write(reinterpret_cast<char *>(&size), 4); std::cout.write(reinterpret_cast<char *>(&size), 4);
std::cout.write(data, size); std::cout.write(data, size);
std::cout.flush(); std::cout.flush();
} }
// SERVER // SERVER
void NativeMessagingServer::start() void NativeMessagingServer::start()
{ {
this->thread.start(); this->thread.start();
} }
void NativeMessagingServer::ReceiverThread::run() void NativeMessagingServer::ReceiverThread::run()
{ {
ipc::message_queue::remove("chatterino_gui"); ipc::message_queue::remove("chatterino_gui");
ipc::message_queue messageQueue(ipc::open_or_create, "chatterino_gui", 100, ipc::message_queue messageQueue(ipc::open_or_create, "chatterino_gui", 100,
MESSAGE_SIZE); MESSAGE_SIZE);
while (true) while (true)
{ {
try try
{ {
auto buf = std::make_unique<char[]>(MESSAGE_SIZE); auto buf = std::make_unique<char[]>(MESSAGE_SIZE);
auto retSize = ipc::message_queue::size_type(); auto retSize = ipc::message_queue::size_type();
auto priority = static_cast<unsigned int>(0); auto priority = static_cast<unsigned int>(0);
messageQueue.receive(buf.get(), MESSAGE_SIZE, retSize, priority); messageQueue.receive(buf.get(), MESSAGE_SIZE, retSize, priority);
auto document = QJsonDocument::fromJson( auto document = QJsonDocument::fromJson(
QByteArray::fromRawData(buf.get(), retSize)); QByteArray::fromRawData(buf.get(), retSize));
this->handleMessage(document.object()); this->handleMessage(document.object());
} }
catch (ipc::interprocess_exception &ex) catch (ipc::interprocess_exception &ex)
{ {
qDebug() << "received from gui process:" << ex.what(); qDebug() << "received from gui process:" << ex.what();
} }
} }
} }
void NativeMessagingServer::ReceiverThread::handleMessage( void NativeMessagingServer::ReceiverThread::handleMessage(
const QJsonObject &root) const QJsonObject &root)
{ {
auto app = getApp(); auto app = getApp();
QString action = root.value("action").toString(); QString action = root.value("action").toString();
if (action.isNull()) if (action.isNull())
{ {
qDebug() << "NM action was null"; qDebug() << "NM action was null";
return; return;
} }
qDebug() << root; qDebug() << root;
if (action == "select") if (action == "select")
{ {
QString _type = root.value("type").toString(); QString _type = root.value("type").toString();
bool attach = root.value("attach").toBool(); bool attach = root.value("attach").toBool();
bool attachFullscreen = root.value("attach_fullscreen").toBool(); bool attachFullscreen = root.value("attach_fullscreen").toBool();
QString name = root.value("name").toString(); QString name = root.value("name").toString();
#ifdef USEWINSDK #ifdef USEWINSDK
AttachedWindow::GetArgs args; AttachedWindow::GetArgs args;
args.winId = root.value("winId").toString(); args.winId = root.value("winId").toString();
args.yOffset = root.value("yOffset").toInt(-1); args.yOffset = root.value("yOffset").toInt(-1);
args.width = root.value("size").toObject().value("width").toInt(-1); args.width = root.value("size").toObject().value("width").toInt(-1);
args.height = root.value("size").toObject().value("height").toInt(-1); args.height = root.value("size").toObject().value("height").toInt(-1);
args.fullscreen = attachFullscreen; args.fullscreen = attachFullscreen;
qDebug() << args.width << args.height << args.winId; qDebug() << args.width << args.height << args.winId;
if (_type.isNull() || args.winId.isNull()) if (_type.isNull() || args.winId.isNull())
{ {
qDebug() << "NM type, name or winId missing"; qDebug() << "NM type, name or winId missing";
attach = false; attach = false;
attachFullscreen = false; attachFullscreen = false;
return; return;
} }
#endif #endif
if (_type == "twitch") if (_type == "twitch")
{ {
postToThread([=] { postToThread([=] {
if (!name.isEmpty()) if (!name.isEmpty())
{ {
app->twitch.server->watchingChannel.reset( app->twitch.server->watchingChannel.reset(
app->twitch.server->getOrAddChannel(name)); app->twitch.server->getOrAddChannel(name));
} }
if (attach || attachFullscreen) if (attach || attachFullscreen)
{ {
#ifdef USEWINSDK #ifdef USEWINSDK
// if (args.height != -1) { // if (args.height != -1) {
auto *window = auto *window =
AttachedWindow::get(::GetForegroundWindow(), args); AttachedWindow::get(::GetForegroundWindow(), args);
if (!name.isEmpty()) if (!name.isEmpty())
{ {
window->setChannel( window->setChannel(
app->twitch.server->getOrAddChannel(name)); app->twitch.server->getOrAddChannel(name));
} }
// } // }
// window->show(); // window->show();
#endif #endif
} }
}); });
} }
else else
{ {
qDebug() << "NM unknown channel type"; qDebug() << "NM unknown channel type";
} }
} }
else if (action == "detach") else if (action == "detach")
{ {
QString winId = root.value("winId").toString(); QString winId = root.value("winId").toString();
if (winId.isNull()) if (winId.isNull())
{ {
qDebug() << "NM winId missing"; qDebug() << "NM winId missing";
return; return;
} }
#ifdef USEWINSDK #ifdef USEWINSDK
postToThread([winId] { postToThread([winId] {
qDebug() << "NW detach"; qDebug() << "NW detach";
AttachedWindow::detach(winId); AttachedWindow::detach(winId);
}); });
#endif #endif
} }
else else
{ {
qDebug() << "NM unknown action " + action; qDebug() << "NM unknown action " + action;
} }
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,38 +1,38 @@
#pragma once #pragma once
#include <QThread> #include <QThread>
namespace chatterino { namespace chatterino {
class Application; class Application;
class Paths; class Paths;
void registerNmHost(Paths &paths); void registerNmHost(Paths &paths);
std::string &getNmQueueName(Paths &paths); std::string &getNmQueueName(Paths &paths);
class NativeMessagingClient final class NativeMessagingClient final
{ {
public: public:
void sendMessage(const QByteArray &array); void sendMessage(const QByteArray &array);
void writeToCout(const QByteArray &array); void writeToCout(const QByteArray &array);
}; };
class NativeMessagingServer final class NativeMessagingServer final
{ {
public: public:
void start(); void start();
private: private:
class ReceiverThread : public QThread class ReceiverThread : public QThread
{ {
public: public:
void run() override; void run() override;
private: private:
void handleMessage(const QJsonObject &root); void handleMessage(const QJsonObject &root);
}; };
ReceiverThread thread; ReceiverThread thread;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,315 +1,315 @@
#include "Updates.hpp" #include "Updates.hpp"
#include "Settings.hpp" #include "Settings.hpp"
#include "common/NetworkRequest.hpp" #include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp" #include "common/Outcome.hpp"
#include "common/Version.hpp" #include "common/Version.hpp"
#include "singletons/Paths.hpp" #include "singletons/Paths.hpp"
#include "util/CombinePath.hpp" #include "util/CombinePath.hpp"
#include "util/PostToThread.hpp" #include "util/PostToThread.hpp"
#include <QDebug> #include <QDebug>
#include <QDesktopServices> #include <QDesktopServices>
#include <QMessageBox> #include <QMessageBox>
#include <QProcess> #include <QProcess>
namespace chatterino { namespace chatterino {
namespace { namespace {
QString currentBranch() QString currentBranch()
{ {
return getSettings()->betaUpdates ? "beta" : "stable"; return getSettings()->betaUpdates ? "beta" : "stable";
} }
} // namespace } // namespace
Updates::Updates() Updates::Updates()
: currentVersion_(CHATTERINO_VERSION) : currentVersion_(CHATTERINO_VERSION)
, updateGuideLink_("https://chatterino.com") , updateGuideLink_("https://chatterino.com")
{ {
qDebug() << "init UpdateManager"; qDebug() << "init UpdateManager";
} }
Updates &Updates::getInstance() Updates &Updates::getInstance()
{ {
// fourtf: don't add this class to the application class // fourtf: don't add this class to the application class
static Updates instance; static Updates instance;
return instance; return instance;
} }
const QString &Updates::getCurrentVersion() const const QString &Updates::getCurrentVersion() const
{ {
return currentVersion_; return currentVersion_;
} }
const QString &Updates::getOnlineVersion() const const QString &Updates::getOnlineVersion() const
{ {
return onlineVersion_; return onlineVersion_;
} }
void Updates::installUpdates() void Updates::installUpdates()
{ {
if (this->status_ != UpdateAvailable) if (this->status_ != UpdateAvailable)
{ {
assert(false); assert(false);
return; return;
} }
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
QMessageBox *box = new QMessageBox( QMessageBox *box = new QMessageBox(
QMessageBox::Information, "Chatterino Update", QMessageBox::Information, "Chatterino Update",
"A link will open in your browser. Download and install to update."); "A link will open in your browser. Download and install to update.");
box->setAttribute(Qt::WA_DeleteOnClose); box->setAttribute(Qt::WA_DeleteOnClose);
box->exec(); box->exec();
QDesktopServices::openUrl(this->updateExe_); QDesktopServices::openUrl(this->updateExe_);
#elif defined Q_OS_LINUX #elif defined Q_OS_LINUX
QMessageBox *box = QMessageBox *box =
new QMessageBox(QMessageBox::Information, "Chatterino Update", new QMessageBox(QMessageBox::Information, "Chatterino Update",
"Automatic updates are currently not available on " "Automatic updates are currently not available on "
"linux. Please redownload the app to update."); "linux. Please redownload the app to update.");
box->setAttribute(Qt::WA_DeleteOnClose); box->setAttribute(Qt::WA_DeleteOnClose);
box->exec(); box->exec();
QDesktopServices::openUrl(this->updateGuideLink_); QDesktopServices::openUrl(this->updateGuideLink_);
#elif defined Q_OS_WIN #elif defined Q_OS_WIN
if (getPaths()->isPortable()) if (getPaths()->isPortable())
{ {
QMessageBox *box = QMessageBox *box =
new QMessageBox(QMessageBox::Information, "Chatterino Update", new QMessageBox(QMessageBox::Information, "Chatterino Update",
"Chatterino is downloading the update " "Chatterino is downloading the update "
"in the background and will run the " "in the background and will run the "
"updater once it is finished."); "updater once it is finished.");
box->setAttribute(Qt::WA_DeleteOnClose); box->setAttribute(Qt::WA_DeleteOnClose);
box->show(); box->show();
NetworkRequest(this->updatePortable_) NetworkRequest(this->updatePortable_)
.timeout(600000) .timeout(600000)
.onError([this](int) -> bool { .onError([this](int) -> bool {
this->setStatus_(DownloadFailed); this->setStatus_(DownloadFailed);
postToThread([] { postToThread([] {
QMessageBox *box = new QMessageBox( QMessageBox *box = new QMessageBox(
QMessageBox::Information, "Chatterino Update", QMessageBox::Information, "Chatterino Update",
"Failed while trying to download the update."); "Failed while trying to download the update.");
box->setAttribute(Qt::WA_DeleteOnClose); box->setAttribute(Qt::WA_DeleteOnClose);
box->show(); box->show();
box->raise(); box->raise();
}); });
return true; return true;
}) })
.onSuccess([this](auto result) -> Outcome { .onSuccess([this](auto result) -> Outcome {
QByteArray object = result.getData(); QByteArray object = result.getData();
auto filename = auto filename =
combinePath(getPaths()->miscDirectory, "update.zip"); combinePath(getPaths()->miscDirectory, "update.zip");
QFile file(filename); QFile file(filename);
file.open(QIODevice::Truncate | QIODevice::WriteOnly); file.open(QIODevice::Truncate | QIODevice::WriteOnly);
if (file.write(object) == -1) if (file.write(object) == -1)
{ {
this->setStatus_(WriteFileFailed); this->setStatus_(WriteFileFailed);
return Failure; return Failure;
} }
file.flush(); file.flush();
file.close(); file.close();
QProcess::startDetached( QProcess::startDetached(
combinePath(QCoreApplication::applicationDirPath(), combinePath(QCoreApplication::applicationDirPath(),
"updater.1/ChatterinoUpdater.exe"), "updater.1/ChatterinoUpdater.exe"),
{filename, "restart"}); {filename, "restart"});
QApplication::exit(0); QApplication::exit(0);
return Success; return Success;
}) })
.execute(); .execute();
this->setStatus_(Downloading); this->setStatus_(Downloading);
} }
else else
{ {
QMessageBox *box = QMessageBox *box =
new QMessageBox(QMessageBox::Information, "Chatterino Update", new QMessageBox(QMessageBox::Information, "Chatterino Update",
"Chatterino is downloading the update " "Chatterino is downloading the update "
"in the background and will run the " "in the background and will run the "
"updater once it is finished."); "updater once it is finished.");
box->setAttribute(Qt::WA_DeleteOnClose); box->setAttribute(Qt::WA_DeleteOnClose);
box->show(); box->show();
NetworkRequest(this->updateExe_) NetworkRequest(this->updateExe_)
.timeout(600000) .timeout(600000)
.onError([this](int) -> bool { .onError([this](int) -> bool {
this->setStatus_(DownloadFailed); this->setStatus_(DownloadFailed);
QMessageBox *box = new QMessageBox( QMessageBox *box = new QMessageBox(
QMessageBox::Information, "Chatterino Update", QMessageBox::Information, "Chatterino Update",
"Failed to download the update. \n\nTry manually " "Failed to download the update. \n\nTry manually "
"downloading the update."); "downloading the update.");
box->setAttribute(Qt::WA_DeleteOnClose); box->setAttribute(Qt::WA_DeleteOnClose);
box->exec(); box->exec();
return true; return true;
}) })
.onSuccess([this](auto result) -> Outcome { .onSuccess([this](auto result) -> Outcome {
QByteArray object = result.getData(); QByteArray object = result.getData();
auto filename = auto filename =
combinePath(getPaths()->miscDirectory, "Update.exe"); combinePath(getPaths()->miscDirectory, "Update.exe");
QFile file(filename); QFile file(filename);
file.open(QIODevice::Truncate | QIODevice::WriteOnly); file.open(QIODevice::Truncate | QIODevice::WriteOnly);
if (file.write(object) == -1) if (file.write(object) == -1)
{ {
this->setStatus_(WriteFileFailed); this->setStatus_(WriteFileFailed);
QMessageBox *box = new QMessageBox( QMessageBox *box = new QMessageBox(
QMessageBox::Information, "Chatterino Update", QMessageBox::Information, "Chatterino Update",
"Failed to save the update file. This could be due to " "Failed to save the update file. This could be due to "
"window settings or antivirus software.\n\nTry " "window settings or antivirus software.\n\nTry "
"manually " "manually "
"downloading the update."); "downloading the update.");
box->setAttribute(Qt::WA_DeleteOnClose); box->setAttribute(Qt::WA_DeleteOnClose);
box->exec(); box->exec();
QDesktopServices::openUrl(this->updateExe_); QDesktopServices::openUrl(this->updateExe_);
return Failure; return Failure;
} }
file.flush(); file.flush();
file.close(); file.close();
if (QProcess::startDetached(filename)) if (QProcess::startDetached(filename))
{ {
QApplication::exit(0); QApplication::exit(0);
} }
else else
{ {
QMessageBox *box = new QMessageBox( QMessageBox *box = new QMessageBox(
QMessageBox::Information, "Chatterino Update", QMessageBox::Information, "Chatterino Update",
"Failed to execute update binary. This could be due to " "Failed to execute update binary. This could be due to "
"window " "window "
"settings or antivirus software.\n\nTry manually " "settings or antivirus software.\n\nTry manually "
"downloading " "downloading "
"the update."); "the update.");
box->setAttribute(Qt::WA_DeleteOnClose); box->setAttribute(Qt::WA_DeleteOnClose);
box->exec(); box->exec();
QDesktopServices::openUrl(this->updateExe_); QDesktopServices::openUrl(this->updateExe_);
} }
return Success; return Success;
}) })
.execute(); .execute();
this->setStatus_(Downloading); this->setStatus_(Downloading);
} }
#endif #endif
} }
void Updates::checkForUpdates() void Updates::checkForUpdates()
{ {
QString url = QString url =
"https://notitia.chatterino.com/version/chatterino/" CHATTERINO_OS "/" + "https://notitia.chatterino.com/version/chatterino/" CHATTERINO_OS "/" +
currentBranch(); currentBranch();
NetworkRequest(url) NetworkRequest(url)
.timeout(60000) .timeout(60000)
.onSuccess([this](auto result) -> Outcome { .onSuccess([this](auto result) -> Outcome {
auto object = result.parseJson(); auto object = result.parseJson();
/// Version available on every platform /// Version available on every platform
QJsonValue version_val = object.value("version"); QJsonValue version_val = object.value("version");
if (!version_val.isString()) if (!version_val.isString())
{ {
this->setStatus_(SearchFailed); this->setStatus_(SearchFailed);
qDebug() << "error updating"; qDebug() << "error updating";
return Failure; return Failure;
} }
#if defined Q_OS_WIN || defined Q_OS_MACOS #if defined Q_OS_WIN || defined Q_OS_MACOS
/// Windows downloads an installer for the new version /// Windows downloads an installer for the new version
QJsonValue updateExe_val = object.value("updateexe"); QJsonValue updateExe_val = object.value("updateexe");
if (!updateExe_val.isString()) if (!updateExe_val.isString())
{ {
this->setStatus_(SearchFailed); this->setStatus_(SearchFailed);
qDebug() << "error updating"; qDebug() << "error updating";
return Failure; return Failure;
} }
this->updateExe_ = updateExe_val.toString(); this->updateExe_ = updateExe_val.toString();
/// Windows portable /// Windows portable
QJsonValue portable_val = object.value("portable_download"); QJsonValue portable_val = object.value("portable_download");
if (!portable_val.isString()) if (!portable_val.isString())
{ {
this->setStatus_(SearchFailed); this->setStatus_(SearchFailed);
qDebug() << "error updating"; qDebug() << "error updating";
return Failure; return Failure;
} }
this->updatePortable_ = portable_val.toString(); this->updatePortable_ = portable_val.toString();
#elif defined Q_OS_LINUX #elif defined Q_OS_LINUX
QJsonValue updateGuide_val = object.value("updateguide"); QJsonValue updateGuide_val = object.value("updateguide");
if (updateGuide_val.isString()) if (updateGuide_val.isString())
{ {
this->updateGuideLink_ = updateGuide_val.toString(); this->updateGuideLink_ = updateGuide_val.toString();
} }
#else #else
return Failure; return Failure;
#endif #endif
/// Current version /// Current version
this->onlineVersion_ = version_val.toString(); this->onlineVersion_ = version_val.toString();
/// Update available :) /// Update available :)
if (this->currentVersion_ != this->onlineVersion_) if (this->currentVersion_ != this->onlineVersion_)
{ {
this->setStatus_(UpdateAvailable); this->setStatus_(UpdateAvailable);
} }
else else
{ {
this->setStatus_(NoUpdateAvailable); this->setStatus_(NoUpdateAvailable);
} }
return Failure; return Failure;
}) })
.execute(); .execute();
this->setStatus_(Searching); this->setStatus_(Searching);
} }
Updates::Status Updates::getStatus() const Updates::Status Updates::getStatus() const
{ {
return this->status_; return this->status_;
} }
bool Updates::shouldShowUpdateButton() const bool Updates::shouldShowUpdateButton() const
{ {
switch (this->getStatus()) switch (this->getStatus())
{ {
case UpdateAvailable: case UpdateAvailable:
case SearchFailed: case SearchFailed:
case Downloading: case Downloading:
case DownloadFailed: case DownloadFailed:
case WriteFileFailed: case WriteFileFailed:
return true; return true;
default: default:
return false; return false;
} }
} }
bool Updates::isError() const bool Updates::isError() const
{ {
switch (this->getStatus()) switch (this->getStatus())
{ {
case SearchFailed: case SearchFailed:
case DownloadFailed: case DownloadFailed:
case WriteFileFailed: case WriteFileFailed:
return true; return true;
default: default:
return false; return false;
} }
} }
void Updates::setStatus_(Status status) void Updates::setStatus_(Status status)
{ {
if (this->status_ != status) if (this->status_ != status)
{ {
this->status_ = status; this->status_ = status;
postToThread([this, status] { this->statusUpdated.invoke(status); }); postToThread([this, status] { this->statusUpdated.invoke(status); });
} }
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,50 +1,50 @@
#pragma once #pragma once
#include <QString> #include <QString>
#include <pajlada/signals/signal.hpp> #include <pajlada/signals/signal.hpp>
namespace chatterino { namespace chatterino {
class Updates class Updates
{ {
Updates(); Updates();
public: public:
enum Status { enum Status {
None, None,
Searching, Searching,
UpdateAvailable, UpdateAvailable,
NoUpdateAvailable, NoUpdateAvailable,
SearchFailed, SearchFailed,
Downloading, Downloading,
DownloadFailed, DownloadFailed,
WriteFileFailed, WriteFileFailed,
}; };
// fourtf: don't add this class to the application class // fourtf: don't add this class to the application class
static Updates &getInstance(); static Updates &getInstance();
void checkForUpdates(); void checkForUpdates();
const QString &getCurrentVersion() const; const QString &getCurrentVersion() const;
const QString &getOnlineVersion() const; const QString &getOnlineVersion() const;
void installUpdates(); void installUpdates();
Status getStatus() const; Status getStatus() const;
bool shouldShowUpdateButton() const; bool shouldShowUpdateButton() const;
bool isError() const; bool isError() const;
pajlada::Signals::Signal<Status> statusUpdated; pajlada::Signals::Signal<Status> statusUpdated;
private: private:
QString currentVersion_; QString currentVersion_;
QString onlineVersion_; QString onlineVersion_;
Status status_ = None; Status status_ = None;
QString updateExe_; QString updateExe_;
QString updatePortable_; QString updatePortable_;
QString updateGuideLink_; QString updateGuideLink_;
void setStatus_(Status status); void setStatus_(Status status);
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,7 +1,7 @@
#include "DebugCount.hpp" #include "DebugCount.hpp"
namespace chatterino { namespace chatterino {
UniqueAccess<QMap<QString, int64_t>> DebugCount::counts_; UniqueAccess<QMap<QString, int64_t>> DebugCount::counts_;
} // namespace chatterino } // namespace chatterino

View file

@ -1,67 +1,67 @@
#pragma once #pragma once
#include <common/UniqueAccess.hpp> #include <common/UniqueAccess.hpp>
#include <mutex> #include <mutex>
#include <typeinfo> #include <typeinfo>
#include <QMap> #include <QMap>
#include <QString> #include <QString>
namespace chatterino { namespace chatterino {
class DebugCount class DebugCount
{ {
public: public:
static void increase(const QString &name) static void increase(const QString &name)
{ {
auto counts = counts_.access(); auto counts = counts_.access();
auto it = counts->find(name); auto it = counts->find(name);
if (it == counts->end()) if (it == counts->end())
{ {
counts->insert(name, 1); counts->insert(name, 1);
} }
else else
{ {
reinterpret_cast<int64_t &>(it.value())++; reinterpret_cast<int64_t &>(it.value())++;
} }
} }
static void decrease(const QString &name) static void decrease(const QString &name)
{ {
auto counts = counts_.access(); auto counts = counts_.access();
auto it = counts->find(name); auto it = counts->find(name);
if (it == counts->end()) if (it == counts->end())
{ {
counts->insert(name, -1); counts->insert(name, -1);
} }
else else
{ {
reinterpret_cast<int64_t &>(it.value())--; reinterpret_cast<int64_t &>(it.value())--;
} }
} }
static QString getDebugText() static QString getDebugText()
{ {
auto counts = counts_.access(); auto counts = counts_.access();
QString text; QString text;
for (auto it = counts->begin(); it != counts->end(); it++) for (auto it = counts->begin(); it != counts->end(); it++)
{ {
text += it.key() + ": " + QString::number(it.value()) + "\n"; text += it.key() + ": " + QString::number(it.value()) + "\n";
} }
return text; return text;
} }
QString toString() QString toString()
{ {
return ""; return "";
} }
private: private:
static UniqueAccess<QMap<QString, int64_t>> counts_; static UniqueAccess<QMap<QString, int64_t>> counts_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,53 +1,53 @@
#include "FormatTime.hpp" #include "FormatTime.hpp"
namespace chatterino { namespace chatterino {
namespace { namespace {
void appendDuration(int count, QChar &&order, QString &outString) void appendDuration(int count, QChar &&order, QString &outString)
{ {
outString.append(QString::number(count)); outString.append(QString::number(count));
outString.append(order); outString.append(order);
} }
} // namespace } // namespace
QString formatTime(int totalSeconds) QString formatTime(int totalSeconds)
{ {
QString res; QString res;
int seconds = totalSeconds % 60; int seconds = totalSeconds % 60;
int timeoutMinutes = totalSeconds / 60; int timeoutMinutes = totalSeconds / 60;
int minutes = timeoutMinutes % 60; int minutes = timeoutMinutes % 60;
int timeoutHours = timeoutMinutes / 60; int timeoutHours = timeoutMinutes / 60;
int hours = timeoutHours % 24; int hours = timeoutHours % 24;
int days = timeoutHours / 24; int days = timeoutHours / 24;
if (days > 0) if (days > 0)
{ {
appendDuration(days, 'd', res); appendDuration(days, 'd', res);
} }
if (hours > 0) if (hours > 0)
{ {
if (!res.isEmpty()) if (!res.isEmpty())
{ {
res.append(" "); res.append(" ");
} }
appendDuration(hours, 'h', res); appendDuration(hours, 'h', res);
} }
if (minutes > 0) if (minutes > 0)
{ {
if (!res.isEmpty()) if (!res.isEmpty())
{ {
res.append(" "); res.append(" ");
} }
appendDuration(minutes, 'm', res); appendDuration(minutes, 'm', res);
} }
if (seconds > 0) if (seconds > 0)
{ {
if (!res.isEmpty()) if (!res.isEmpty())
{ {
res.append(" "); res.append(" ");
} }
appendDuration(seconds, 's', res); appendDuration(seconds, 's', res);
} }
return res; return res;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,10 +1,10 @@
#pragma once #pragma once
#include <QString> #include <QString>
namespace chatterino { namespace chatterino {
// format: 1h 23m 42s // format: 1h 23m 42s
QString formatTime(int totalSeconds); QString formatTime(int totalSeconds);
} // namespace chatterino } // namespace chatterino

View file

@ -1,99 +1,99 @@
#include "IncognitoBrowser.hpp" #include "IncognitoBrowser.hpp"
#include <QProcess> #include <QProcess>
#include <QRegularExpression> #include <QRegularExpression>
#include <QSettings> #include <QSettings>
#include <QVariant> #include <QVariant>
#include "debug/Log.hpp" #include "debug/Log.hpp"
namespace chatterino { namespace chatterino {
namespace { namespace {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QString injectPrivateSwitch(QString command) QString injectPrivateSwitch(QString command)
{ {
// list of command line switches to turn on private browsing in browsers // list of command line switches to turn on private browsing in browsers
static auto switches = std::vector<std::pair<QString, QString>>{ static auto switches = std::vector<std::pair<QString, QString>>{
{"firefox", "-private-window"}, {"chrome", "-incognito"}, {"firefox", "-private-window"}, {"chrome", "-incognito"},
{"vivaldi", "-incognito"}, {"opera", "-newprivatetab"}, {"vivaldi", "-incognito"}, {"opera", "-newprivatetab"},
{"opera\\\\launcher", "--private"}, {"iexplore", "-private"}, {"opera\\\\launcher", "--private"}, {"iexplore", "-private"},
}; };
// transform into regex and replacement string // transform into regex and replacement string
std::vector<std::pair<QRegularExpression, QString>> replacers; std::vector<std::pair<QRegularExpression, QString>> replacers;
for (const auto &switch_ : switches) for (const auto &switch_ : switches)
{ {
replacers.emplace_back( replacers.emplace_back(
QRegularExpression("(" + switch_.first + "\\.exe\"?).*", QRegularExpression("(" + switch_.first + "\\.exe\"?).*",
QRegularExpression::CaseInsensitiveOption), QRegularExpression::CaseInsensitiveOption),
"\\1 " + switch_.second); "\\1 " + switch_.second);
} }
// try to find matching regex and apply it // try to find matching regex and apply it
for (const auto &replacement : replacers) for (const auto &replacement : replacers)
{ {
if (replacement.first.match(command).hasMatch()) if (replacement.first.match(command).hasMatch())
{ {
command.replace(replacement.first, replacement.second); command.replace(replacement.first, replacement.second);
return command; return command;
} }
} }
// couldn't match any browser -> unknown browser // couldn't match any browser -> unknown browser
return QString(); return QString();
} }
QString getCommand(const QString &link) QString getCommand(const QString &link)
{ {
// get default browser prog id // get default browser prog id
auto browserId = QSettings("HKEY_CURRENT_" auto browserId = QSettings("HKEY_CURRENT_"
"USER\\Software\\Microsoft\\Windows\\Shell\\" "USER\\Software\\Microsoft\\Windows\\Shell\\"
"Associations\\UrlAssociatio" "Associations\\UrlAssociatio"
"ns\\http\\UserChoice", "ns\\http\\UserChoice",
QSettings::NativeFormat) QSettings::NativeFormat)
.value("Progid") .value("Progid")
.toString(); .toString();
// get default browser start command // get default browser start command
auto command = QSettings("HKEY_CLASSES_ROOT\\" + browserId + auto command = QSettings("HKEY_CLASSES_ROOT\\" + browserId +
"\\shell\\open\\command", "\\shell\\open\\command",
QSettings::NativeFormat) QSettings::NativeFormat)
.value("Default") .value("Default")
.toString(); .toString();
if (command.isNull()) if (command.isNull())
return QString(); return QString();
log(command); log(command);
// inject switch to enable private browsing // inject switch to enable private browsing
command = injectPrivateSwitch(command); command = injectPrivateSwitch(command);
if (command.isNull()) if (command.isNull())
return QString(); return QString();
// link // link
command += " " + link; command += " " + link;
return command; return command;
} }
#endif #endif
} // namespace } // namespace
bool supportsIncognitoLinks() bool supportsIncognitoLinks()
{ {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
return !getCommand("").isNull(); return !getCommand("").isNull();
#else #else
return false; return false;
#endif #endif
} }
void openLinkIncognito(const QString &link) void openLinkIncognito(const QString &link)
{ {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
auto command = getCommand(link); auto command = getCommand(link);
QProcess::startDetached(command); QProcess::startDetached(command);
#endif #endif
} }
} // namespace chatterino } // namespace chatterino

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