mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Normalize line endings in already existing files
This commit is contained in:
parent
8064f8a49e
commit
b06eb9df83
157 changed files with 15175 additions and 15175 deletions
118
appveyor.yml
118
appveyor.yml
|
@ -1,59 +1,59 @@
|
|||
version: "{build}"
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
image: Visual Studio 2017
|
||||
platform: Any CPU
|
||||
clone_depth: 1
|
||||
init:
|
||||
- cmd: ''
|
||||
install:
|
||||
- cmd: >-
|
||||
git submodule update --init --recursive
|
||||
|
||||
set QTDIR=C:\Qt\5.11\msvc2017_64
|
||||
|
||||
set PATH=%PATH%;%QTDIR%\bin
|
||||
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
|
||||
pip install conan -q
|
||||
build_script:
|
||||
- cmd: >-
|
||||
dir
|
||||
|
||||
mkdir build
|
||||
|
||||
cd build
|
||||
|
||||
conan install ..
|
||||
|
||||
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)\\\""
|
||||
|
||||
set cl=/MP
|
||||
|
||||
nmake /S /NOLOGO
|
||||
|
||||
windeployqt release/chatterino.exe --release --no-compiler-runtime --no-translations --no-opengl-sw --dir Chatterino2/
|
||||
|
||||
cp release/chatterino.exe Chatterino2/
|
||||
|
||||
7z a chatterino-windows-x86-64.zip Chatterino2/
|
||||
artifacts:
|
||||
- path: build/chatterino-windows-x86-64.zip
|
||||
name: chatterino
|
||||
deploy:
|
||||
- provider: GitHub
|
||||
tag: 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)'
|
||||
auth_token:
|
||||
secure: sAJzAbiQSsYZLT+byDar9u61X0E9o35anaPMSFkOzdHeDFHjx1kW4cDP/4EEbxhx
|
||||
repository: Chatterino/chatterino2
|
||||
artifact: build/chatterino-windows-x86-64.zip
|
||||
prerelease: true
|
||||
force_update: true
|
||||
on:
|
||||
branch: master
|
||||
version: "{build}"
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
image: Visual Studio 2017
|
||||
platform: Any CPU
|
||||
clone_depth: 1
|
||||
init:
|
||||
- cmd: ''
|
||||
install:
|
||||
- cmd: >-
|
||||
git submodule update --init --recursive
|
||||
|
||||
set QTDIR=C:\Qt\5.11\msvc2017_64
|
||||
|
||||
set PATH=%PATH%;%QTDIR%\bin
|
||||
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
|
||||
pip install conan -q
|
||||
build_script:
|
||||
- cmd: >-
|
||||
dir
|
||||
|
||||
mkdir build
|
||||
|
||||
cd build
|
||||
|
||||
conan install ..
|
||||
|
||||
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)\\\""
|
||||
|
||||
set cl=/MP
|
||||
|
||||
nmake /S /NOLOGO
|
||||
|
||||
windeployqt release/chatterino.exe --release --no-compiler-runtime --no-translations --no-opengl-sw --dir Chatterino2/
|
||||
|
||||
cp release/chatterino.exe Chatterino2/
|
||||
|
||||
7z a chatterino-windows-x86-64.zip Chatterino2/
|
||||
artifacts:
|
||||
- path: build/chatterino-windows-x86-64.zip
|
||||
name: chatterino
|
||||
deploy:
|
||||
- provider: GitHub
|
||||
tag: 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)'
|
||||
auth_token:
|
||||
secure: sAJzAbiQSsYZLT+byDar9u61X0E9o35anaPMSFkOzdHeDFHjx1kW4cDP/4EEbxhx
|
||||
repository: Chatterino/chatterino2
|
||||
artifact: build/chatterino-windows-x86-64.zip
|
||||
prerelease: true
|
||||
force_update: true
|
||||
on:
|
||||
branch: master
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
[requires]
|
||||
OpenSSL/1.0.2o@conan/stable
|
||||
boost/1.69.0@conan/stable
|
||||
|
||||
[generators]
|
||||
qmake
|
||||
|
||||
[options]
|
||||
OpenSSL:shared=True
|
||||
|
||||
[imports]
|
||||
bin, *.dll -> ./Chatterino2 @ keep_path=False
|
||||
[requires]
|
||||
OpenSSL/1.0.2o@conan/stable
|
||||
boost/1.69.0@conan/stable
|
||||
|
||||
[generators]
|
||||
qmake
|
||||
|
||||
[options]
|
||||
OpenSSL:shared=True
|
||||
|
||||
[imports]
|
||||
bin, *.dll -> ./Chatterino2 @ keep_path=False
|
||||
|
|
|
@ -1,128 +1,128 @@
|
|||
#include "BaseSettings.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "util/Clamp.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
std::vector<std::weak_ptr<pajlada::Settings::SettingData>> _settings;
|
||||
|
||||
AB_SETTINGS_CLASS *AB_SETTINGS_CLASS::instance = nullptr;
|
||||
|
||||
void _actuallyRegisterSetting(
|
||||
std::weak_ptr<pajlada::Settings::SettingData> setting)
|
||||
{
|
||||
_settings.push_back(setting);
|
||||
}
|
||||
|
||||
AB_SETTINGS_CLASS::AB_SETTINGS_CLASS(const QString &settingsDirectory)
|
||||
{
|
||||
AB_SETTINGS_CLASS::instance = this;
|
||||
|
||||
QString settingsPath = settingsDirectory + "/settings.json";
|
||||
|
||||
// get global instance of the settings library
|
||||
auto settingsInstance = pajlada::Settings::SettingManager::getInstance();
|
||||
|
||||
settingsInstance->load(qPrintable(settingsPath));
|
||||
|
||||
settingsInstance->setBackupEnabled(true);
|
||||
settingsInstance->setBackupSlots(9);
|
||||
settingsInstance->saveMethod =
|
||||
pajlada::Settings::SettingManager::SaveMethod::SaveOnExit;
|
||||
}
|
||||
|
||||
void AB_SETTINGS_CLASS::saveSnapshot()
|
||||
{
|
||||
rapidjson::Document *d = new rapidjson::Document(rapidjson::kObjectType);
|
||||
rapidjson::Document::AllocatorType &a = d->GetAllocator();
|
||||
|
||||
for (const auto &weakSetting : _settings)
|
||||
{
|
||||
auto setting = weakSetting.lock();
|
||||
if (!setting)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
rapidjson::Value key(setting->getPath().c_str(), a);
|
||||
auto curVal = setting->unmarshalJSON();
|
||||
if (curVal == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
rapidjson::Value val;
|
||||
val.CopyFrom(*curVal, a);
|
||||
d->AddMember(key.Move(), val.Move(), a);
|
||||
}
|
||||
|
||||
// log("Snapshot state: {}", rj::stringify(*d));
|
||||
|
||||
this->snapshot_.reset(d);
|
||||
}
|
||||
|
||||
void AB_SETTINGS_CLASS::restoreSnapshot()
|
||||
{
|
||||
if (!this->snapshot_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &snapshot = *(this->snapshot_.get());
|
||||
|
||||
if (!snapshot.IsObject())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &weakSetting : _settings)
|
||||
{
|
||||
auto setting = weakSetting.lock();
|
||||
if (!setting)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *path = setting->getPath().c_str();
|
||||
|
||||
if (!snapshot.HasMember(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
setting->marshalJSON(snapshot[path]);
|
||||
}
|
||||
}
|
||||
|
||||
float AB_SETTINGS_CLASS::getClampedUiScale() const
|
||||
{
|
||||
return clamp<float>(this->uiScale.getValue(), 0.2f, 10);
|
||||
}
|
||||
|
||||
void AB_SETTINGS_CLASS::setClampedUiScale(float value)
|
||||
{
|
||||
this->uiScale.setValue(clamp<float>(value, 0.2f, 10));
|
||||
}
|
||||
|
||||
#ifndef AB_CUSTOM_SETTINGS
|
||||
Settings *getSettings()
|
||||
{
|
||||
static_assert(std::is_same_v<AB_SETTINGS_CLASS, Settings>,
|
||||
"`AB_SETTINGS_CLASS` must be the same as `Settings`");
|
||||
|
||||
assert(AB_SETTINGS_CLASS::instance);
|
||||
|
||||
return AB_SETTINGS_CLASS::instance;
|
||||
}
|
||||
#endif
|
||||
|
||||
AB_SETTINGS_CLASS *getABSettings()
|
||||
{
|
||||
assert(AB_SETTINGS_CLASS::instance);
|
||||
|
||||
return AB_SETTINGS_CLASS::instance;
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
#include "BaseSettings.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "util/Clamp.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
std::vector<std::weak_ptr<pajlada::Settings::SettingData>> _settings;
|
||||
|
||||
AB_SETTINGS_CLASS *AB_SETTINGS_CLASS::instance = nullptr;
|
||||
|
||||
void _actuallyRegisterSetting(
|
||||
std::weak_ptr<pajlada::Settings::SettingData> setting)
|
||||
{
|
||||
_settings.push_back(setting);
|
||||
}
|
||||
|
||||
AB_SETTINGS_CLASS::AB_SETTINGS_CLASS(const QString &settingsDirectory)
|
||||
{
|
||||
AB_SETTINGS_CLASS::instance = this;
|
||||
|
||||
QString settingsPath = settingsDirectory + "/settings.json";
|
||||
|
||||
// get global instance of the settings library
|
||||
auto settingsInstance = pajlada::Settings::SettingManager::getInstance();
|
||||
|
||||
settingsInstance->load(qPrintable(settingsPath));
|
||||
|
||||
settingsInstance->setBackupEnabled(true);
|
||||
settingsInstance->setBackupSlots(9);
|
||||
settingsInstance->saveMethod =
|
||||
pajlada::Settings::SettingManager::SaveMethod::SaveOnExit;
|
||||
}
|
||||
|
||||
void AB_SETTINGS_CLASS::saveSnapshot()
|
||||
{
|
||||
rapidjson::Document *d = new rapidjson::Document(rapidjson::kObjectType);
|
||||
rapidjson::Document::AllocatorType &a = d->GetAllocator();
|
||||
|
||||
for (const auto &weakSetting : _settings)
|
||||
{
|
||||
auto setting = weakSetting.lock();
|
||||
if (!setting)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
rapidjson::Value key(setting->getPath().c_str(), a);
|
||||
auto curVal = setting->unmarshalJSON();
|
||||
if (curVal == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
rapidjson::Value val;
|
||||
val.CopyFrom(*curVal, a);
|
||||
d->AddMember(key.Move(), val.Move(), a);
|
||||
}
|
||||
|
||||
// log("Snapshot state: {}", rj::stringify(*d));
|
||||
|
||||
this->snapshot_.reset(d);
|
||||
}
|
||||
|
||||
void AB_SETTINGS_CLASS::restoreSnapshot()
|
||||
{
|
||||
if (!this->snapshot_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &snapshot = *(this->snapshot_.get());
|
||||
|
||||
if (!snapshot.IsObject())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &weakSetting : _settings)
|
||||
{
|
||||
auto setting = weakSetting.lock();
|
||||
if (!setting)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *path = setting->getPath().c_str();
|
||||
|
||||
if (!snapshot.HasMember(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
setting->marshalJSON(snapshot[path]);
|
||||
}
|
||||
}
|
||||
|
||||
float AB_SETTINGS_CLASS::getClampedUiScale() const
|
||||
{
|
||||
return clamp<float>(this->uiScale.getValue(), 0.2f, 10);
|
||||
}
|
||||
|
||||
void AB_SETTINGS_CLASS::setClampedUiScale(float value)
|
||||
{
|
||||
this->uiScale.setValue(clamp<float>(value, 0.2f, 10));
|
||||
}
|
||||
|
||||
#ifndef AB_CUSTOM_SETTINGS
|
||||
Settings *getSettings()
|
||||
{
|
||||
static_assert(std::is_same_v<AB_SETTINGS_CLASS, Settings>,
|
||||
"`AB_SETTINGS_CLASS` must be the same as `Settings`");
|
||||
|
||||
assert(AB_SETTINGS_CLASS::instance);
|
||||
|
||||
return AB_SETTINGS_CLASS::instance;
|
||||
}
|
||||
#endif
|
||||
|
||||
AB_SETTINGS_CLASS *getABSettings()
|
||||
{
|
||||
assert(AB_SETTINGS_CLASS::instance);
|
||||
|
||||
return AB_SETTINGS_CLASS::instance;
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
#ifndef AB_SETTINGS_H
|
||||
#define AB_SETTINGS_H
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
#include <pajlada/settings/settingdata.hpp>
|
||||
|
||||
#include "common/ChatterinoSetting.hpp"
|
||||
|
||||
#ifdef AB_CUSTOM_SETTINGS
|
||||
# define AB_SETTINGS_CLASS ABSettings
|
||||
#else
|
||||
# define AB_SETTINGS_CLASS Settings
|
||||
#endif
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
class Settings;
|
||||
|
||||
void _actuallyRegisterSetting(
|
||||
std::weak_ptr<pajlada::Settings::SettingData> setting);
|
||||
|
||||
class AB_SETTINGS_CLASS
|
||||
{
|
||||
public:
|
||||
AB_SETTINGS_CLASS(const QString &settingsDirectory);
|
||||
|
||||
void saveSnapshot();
|
||||
void restoreSnapshot();
|
||||
|
||||
static AB_SETTINGS_CLASS *instance;
|
||||
|
||||
FloatSetting uiScale = {"/appearance/uiScale2", 1};
|
||||
BoolSetting windowTopMost = {"/appearance/windowAlwaysOnTop", false};
|
||||
|
||||
float getClampedUiScale() const;
|
||||
void setClampedUiScale(float value);
|
||||
|
||||
private:
|
||||
std::unique_ptr<rapidjson::Document> snapshot_;
|
||||
};
|
||||
|
||||
Settings *getSettings();
|
||||
AB_SETTINGS_CLASS *getABSettings();
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
||||
#ifdef CHATTERINO
|
||||
# include "singletons/Settings.hpp"
|
||||
#endif
|
||||
#endif
|
||||
#ifndef AB_SETTINGS_H
|
||||
#define AB_SETTINGS_H
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
#include <pajlada/settings/settingdata.hpp>
|
||||
|
||||
#include "common/ChatterinoSetting.hpp"
|
||||
|
||||
#ifdef AB_CUSTOM_SETTINGS
|
||||
# define AB_SETTINGS_CLASS ABSettings
|
||||
#else
|
||||
# define AB_SETTINGS_CLASS Settings
|
||||
#endif
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
class Settings;
|
||||
|
||||
void _actuallyRegisterSetting(
|
||||
std::weak_ptr<pajlada::Settings::SettingData> setting);
|
||||
|
||||
class AB_SETTINGS_CLASS
|
||||
{
|
||||
public:
|
||||
AB_SETTINGS_CLASS(const QString &settingsDirectory);
|
||||
|
||||
void saveSnapshot();
|
||||
void restoreSnapshot();
|
||||
|
||||
static AB_SETTINGS_CLASS *instance;
|
||||
|
||||
FloatSetting uiScale = {"/appearance/uiScale2", 1};
|
||||
BoolSetting windowTopMost = {"/appearance/windowAlwaysOnTop", false};
|
||||
|
||||
float getClampedUiScale() const;
|
||||
void setClampedUiScale(float value);
|
||||
|
||||
private:
|
||||
std::unique_ptr<rapidjson::Document> snapshot_;
|
||||
};
|
||||
|
||||
Settings *getSettings();
|
||||
AB_SETTINGS_CLASS *getABSettings();
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
||||
#ifdef CHATTERINO
|
||||
# include "singletons/Settings.hpp"
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -1,225 +1,225 @@
|
|||
#include "BaseTheme.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
namespace {
|
||||
double getMultiplierByTheme(const QString &themeName)
|
||||
{
|
||||
if (themeName == "Light")
|
||||
{
|
||||
return 0.8;
|
||||
}
|
||||
else if (themeName == "White")
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
else if (themeName == "Black")
|
||||
{
|
||||
return -1.0;
|
||||
}
|
||||
else if (themeName == "Dark")
|
||||
{
|
||||
return -0.8;
|
||||
}
|
||||
/*
|
||||
else if (themeName == "Custom")
|
||||
{
|
||||
return getSettings()->customThemeMultiplier.getValue();
|
||||
}
|
||||
*/
|
||||
|
||||
return -0.8;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool AB_THEME_CLASS::isLightTheme() const
|
||||
{
|
||||
return this->isLight_;
|
||||
}
|
||||
|
||||
void AB_THEME_CLASS::update()
|
||||
{
|
||||
this->actuallyUpdate(this->themeHue,
|
||||
getMultiplierByTheme(this->themeName.getValue()));
|
||||
|
||||
this->updated.invoke();
|
||||
}
|
||||
|
||||
void AB_THEME_CLASS::actuallyUpdate(double hue, double multiplier)
|
||||
{
|
||||
this->isLight_ = multiplier > 0;
|
||||
bool lightWin = isLight_;
|
||||
|
||||
// QColor themeColor = QColor::fromHslF(hue, 0.43, 0.5);
|
||||
QColor themeColor = QColor::fromHslF(hue, 0.8, 0.5);
|
||||
QColor themeColorNoSat = QColor::fromHslF(hue, 0, 0.5);
|
||||
|
||||
qreal sat = 0;
|
||||
// 0.05;
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
/// WINDOW
|
||||
{
|
||||
#ifdef Q_OS_LINUX
|
||||
this->window.background = lightWin ? "#fff" : QColor(61, 60, 56);
|
||||
#else
|
||||
this->window.background = lightWin ? "#fff" : "#111";
|
||||
#endif
|
||||
|
||||
QColor fg = this->window.text = lightWin ? "#000" : "#eee";
|
||||
this->window.borderFocused = lightWin ? "#ccc" : themeColor;
|
||||
this->window.borderUnfocused = lightWin ? "#ccc" : themeColorNoSat;
|
||||
|
||||
// Ubuntu style
|
||||
// TODO: add setting for this
|
||||
// TabText = QColor(210, 210, 210);
|
||||
// TabBackground = QColor(61, 60, 56);
|
||||
// TabHoverText = QColor(210, 210, 210);
|
||||
// TabHoverBackground = QColor(73, 72, 68);
|
||||
|
||||
// message (referenced later)
|
||||
this->messages.textColors.caret = //
|
||||
this->messages.textColors.regular = isLight_ ? "#000" : "#fff";
|
||||
|
||||
QColor highlighted = lightWin ? QColor("#ff0000") : QColor("#ee6166");
|
||||
|
||||
/// TABS
|
||||
if (lightWin)
|
||||
{
|
||||
this->tabs.regular = {
|
||||
QColor("#444"),
|
||||
{QColor("#fff"), QColor("#eee"), QColor("#fff")},
|
||||
{QColor("#fff"), QColor("#fff"), QColor("#fff")}};
|
||||
this->tabs.newMessage = {
|
||||
QColor("#222"),
|
||||
{QColor("#fff"), QColor("#eee"), QColor("#fff")},
|
||||
{QColor("#bbb"), QColor("#bbb"), QColor("#bbb")}};
|
||||
this->tabs.highlighted = {
|
||||
fg,
|
||||
{QColor("#fff"), QColor("#eee"), QColor("#fff")},
|
||||
{highlighted, highlighted, highlighted}};
|
||||
this->tabs.selected = {
|
||||
QColor("#000"),
|
||||
{QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")},
|
||||
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
|
||||
}
|
||||
else
|
||||
{
|
||||
this->tabs.regular = {
|
||||
QColor("#aaa"),
|
||||
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
|
||||
{QColor("#444"), QColor("#444"), QColor("#444")}};
|
||||
this->tabs.newMessage = {
|
||||
fg,
|
||||
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
|
||||
{QColor("#888"), QColor("#888"), QColor("#888")}};
|
||||
this->tabs.highlighted = {
|
||||
fg,
|
||||
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
|
||||
{highlighted, highlighted, highlighted}};
|
||||
|
||||
this->tabs.selected = {
|
||||
QColor("#fff"),
|
||||
{QColor("#555555"), QColor("#555555"), QColor("#555555")},
|
||||
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
|
||||
}
|
||||
|
||||
// scrollbar
|
||||
this->scrollbars.highlights.highlight = QColor("#ee6166");
|
||||
this->scrollbars.highlights.subscription = QColor("#C466FF");
|
||||
|
||||
// this->tabs.newMessage = {
|
||||
// fg,
|
||||
// {QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
|
||||
// QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
|
||||
// QBrush(blendColors(themeColorNoSat, "#ccc", 0.9),
|
||||
// Qt::FDiagPattern)}};
|
||||
|
||||
// this->tabs.newMessage = {
|
||||
// fg,
|
||||
// {QBrush(blendColors(themeColor, "#666", 0.7),
|
||||
// Qt::FDiagPattern),
|
||||
// QBrush(blendColors(themeColor, "#666", 0.5),
|
||||
// Qt::FDiagPattern),
|
||||
// QBrush(blendColors(themeColorNoSat, "#666", 0.7),
|
||||
// Qt::FDiagPattern)}};
|
||||
// this->tabs.highlighted = {fg, {QColor("#777"),
|
||||
// QColor("#777"), QColor("#666")}};
|
||||
|
||||
this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color();
|
||||
} // namespace AB_NAMESPACE
|
||||
|
||||
// Split
|
||||
bool flat = isLight_;
|
||||
|
||||
// Message
|
||||
this->messages.textColors.link =
|
||||
isLight_ ? QColor(66, 134, 244) : QColor(66, 134, 244);
|
||||
this->messages.textColors.system = QColor(140, 127, 127);
|
||||
|
||||
this->messages.backgrounds.regular = getColor(0, sat, 1);
|
||||
this->messages.backgrounds.alternate = getColor(0, sat, 0.96);
|
||||
|
||||
if (isLight_)
|
||||
{
|
||||
this->messages.backgrounds.highlighted =
|
||||
blendColors(themeColor, this->messages.backgrounds.regular, 0.8);
|
||||
}
|
||||
else
|
||||
{
|
||||
// REMOVED
|
||||
// this->messages.backgrounds.highlighted =
|
||||
// QColor(getSettings()->highlightColor);
|
||||
}
|
||||
|
||||
this->messages.backgrounds.subscription =
|
||||
blendColors(QColor("#C466FF"), this->messages.backgrounds.regular, 0.7);
|
||||
|
||||
// this->messages.backgrounds.resub
|
||||
// this->messages.backgrounds.whisper
|
||||
this->messages.disabled = getColor(0, sat, 1, 0.6);
|
||||
// this->messages.seperator =
|
||||
// this->messages.seperatorInner =
|
||||
|
||||
// Scrollbar
|
||||
this->scrollbars.background = QColor(0, 0, 0, 0);
|
||||
// this->scrollbars.background = splits.background;
|
||||
// this->scrollbars.background.setAlphaF(qreal(0.2));
|
||||
this->scrollbars.thumb = getColor(0, sat, 0.70);
|
||||
this->scrollbars.thumbSelected = getColor(0, sat, 0.65);
|
||||
|
||||
// tooltip
|
||||
this->tooltip.background = QColor(0, 0, 0);
|
||||
this->tooltip.text = QColor(255, 255, 255);
|
||||
|
||||
// Selection
|
||||
this->messages.selection =
|
||||
isLightTheme() ? QColor(0, 0, 0, 64) : QColor(255, 255, 255, 64);
|
||||
}
|
||||
|
||||
QColor AB_THEME_CLASS::blendColors(const QColor &color1, const QColor &color2,
|
||||
qreal ratio)
|
||||
{
|
||||
int r = int(color1.red() * (1 - ratio) + color2.red() * ratio);
|
||||
int g = int(color1.green() * (1 - ratio) + color2.green() * ratio);
|
||||
int b = int(color1.blue() * (1 - ratio) + color2.blue() * ratio);
|
||||
|
||||
return QColor(r, g, b, 255);
|
||||
}
|
||||
|
||||
#ifndef AB_CUSTOM_THEME
|
||||
Theme *getTheme()
|
||||
{
|
||||
static auto theme = [] {
|
||||
auto theme = new Theme();
|
||||
theme->update();
|
||||
return theme;
|
||||
}();
|
||||
|
||||
return theme;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
#include "BaseTheme.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
namespace {
|
||||
double getMultiplierByTheme(const QString &themeName)
|
||||
{
|
||||
if (themeName == "Light")
|
||||
{
|
||||
return 0.8;
|
||||
}
|
||||
else if (themeName == "White")
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
else if (themeName == "Black")
|
||||
{
|
||||
return -1.0;
|
||||
}
|
||||
else if (themeName == "Dark")
|
||||
{
|
||||
return -0.8;
|
||||
}
|
||||
/*
|
||||
else if (themeName == "Custom")
|
||||
{
|
||||
return getSettings()->customThemeMultiplier.getValue();
|
||||
}
|
||||
*/
|
||||
|
||||
return -0.8;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool AB_THEME_CLASS::isLightTheme() const
|
||||
{
|
||||
return this->isLight_;
|
||||
}
|
||||
|
||||
void AB_THEME_CLASS::update()
|
||||
{
|
||||
this->actuallyUpdate(this->themeHue,
|
||||
getMultiplierByTheme(this->themeName.getValue()));
|
||||
|
||||
this->updated.invoke();
|
||||
}
|
||||
|
||||
void AB_THEME_CLASS::actuallyUpdate(double hue, double multiplier)
|
||||
{
|
||||
this->isLight_ = multiplier > 0;
|
||||
bool lightWin = isLight_;
|
||||
|
||||
// QColor themeColor = QColor::fromHslF(hue, 0.43, 0.5);
|
||||
QColor themeColor = QColor::fromHslF(hue, 0.8, 0.5);
|
||||
QColor themeColorNoSat = QColor::fromHslF(hue, 0, 0.5);
|
||||
|
||||
qreal sat = 0;
|
||||
// 0.05;
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
/// WINDOW
|
||||
{
|
||||
#ifdef Q_OS_LINUX
|
||||
this->window.background = lightWin ? "#fff" : QColor(61, 60, 56);
|
||||
#else
|
||||
this->window.background = lightWin ? "#fff" : "#111";
|
||||
#endif
|
||||
|
||||
QColor fg = this->window.text = lightWin ? "#000" : "#eee";
|
||||
this->window.borderFocused = lightWin ? "#ccc" : themeColor;
|
||||
this->window.borderUnfocused = lightWin ? "#ccc" : themeColorNoSat;
|
||||
|
||||
// Ubuntu style
|
||||
// TODO: add setting for this
|
||||
// TabText = QColor(210, 210, 210);
|
||||
// TabBackground = QColor(61, 60, 56);
|
||||
// TabHoverText = QColor(210, 210, 210);
|
||||
// TabHoverBackground = QColor(73, 72, 68);
|
||||
|
||||
// message (referenced later)
|
||||
this->messages.textColors.caret = //
|
||||
this->messages.textColors.regular = isLight_ ? "#000" : "#fff";
|
||||
|
||||
QColor highlighted = lightWin ? QColor("#ff0000") : QColor("#ee6166");
|
||||
|
||||
/// TABS
|
||||
if (lightWin)
|
||||
{
|
||||
this->tabs.regular = {
|
||||
QColor("#444"),
|
||||
{QColor("#fff"), QColor("#eee"), QColor("#fff")},
|
||||
{QColor("#fff"), QColor("#fff"), QColor("#fff")}};
|
||||
this->tabs.newMessage = {
|
||||
QColor("#222"),
|
||||
{QColor("#fff"), QColor("#eee"), QColor("#fff")},
|
||||
{QColor("#bbb"), QColor("#bbb"), QColor("#bbb")}};
|
||||
this->tabs.highlighted = {
|
||||
fg,
|
||||
{QColor("#fff"), QColor("#eee"), QColor("#fff")},
|
||||
{highlighted, highlighted, highlighted}};
|
||||
this->tabs.selected = {
|
||||
QColor("#000"),
|
||||
{QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")},
|
||||
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
|
||||
}
|
||||
else
|
||||
{
|
||||
this->tabs.regular = {
|
||||
QColor("#aaa"),
|
||||
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
|
||||
{QColor("#444"), QColor("#444"), QColor("#444")}};
|
||||
this->tabs.newMessage = {
|
||||
fg,
|
||||
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
|
||||
{QColor("#888"), QColor("#888"), QColor("#888")}};
|
||||
this->tabs.highlighted = {
|
||||
fg,
|
||||
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
|
||||
{highlighted, highlighted, highlighted}};
|
||||
|
||||
this->tabs.selected = {
|
||||
QColor("#fff"),
|
||||
{QColor("#555555"), QColor("#555555"), QColor("#555555")},
|
||||
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
|
||||
}
|
||||
|
||||
// scrollbar
|
||||
this->scrollbars.highlights.highlight = QColor("#ee6166");
|
||||
this->scrollbars.highlights.subscription = QColor("#C466FF");
|
||||
|
||||
// this->tabs.newMessage = {
|
||||
// fg,
|
||||
// {QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
|
||||
// QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
|
||||
// QBrush(blendColors(themeColorNoSat, "#ccc", 0.9),
|
||||
// Qt::FDiagPattern)}};
|
||||
|
||||
// this->tabs.newMessage = {
|
||||
// fg,
|
||||
// {QBrush(blendColors(themeColor, "#666", 0.7),
|
||||
// Qt::FDiagPattern),
|
||||
// QBrush(blendColors(themeColor, "#666", 0.5),
|
||||
// Qt::FDiagPattern),
|
||||
// QBrush(blendColors(themeColorNoSat, "#666", 0.7),
|
||||
// Qt::FDiagPattern)}};
|
||||
// this->tabs.highlighted = {fg, {QColor("#777"),
|
||||
// QColor("#777"), QColor("#666")}};
|
||||
|
||||
this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color();
|
||||
} // namespace AB_NAMESPACE
|
||||
|
||||
// Split
|
||||
bool flat = isLight_;
|
||||
|
||||
// Message
|
||||
this->messages.textColors.link =
|
||||
isLight_ ? QColor(66, 134, 244) : QColor(66, 134, 244);
|
||||
this->messages.textColors.system = QColor(140, 127, 127);
|
||||
|
||||
this->messages.backgrounds.regular = getColor(0, sat, 1);
|
||||
this->messages.backgrounds.alternate = getColor(0, sat, 0.96);
|
||||
|
||||
if (isLight_)
|
||||
{
|
||||
this->messages.backgrounds.highlighted =
|
||||
blendColors(themeColor, this->messages.backgrounds.regular, 0.8);
|
||||
}
|
||||
else
|
||||
{
|
||||
// REMOVED
|
||||
// this->messages.backgrounds.highlighted =
|
||||
// QColor(getSettings()->highlightColor);
|
||||
}
|
||||
|
||||
this->messages.backgrounds.subscription =
|
||||
blendColors(QColor("#C466FF"), this->messages.backgrounds.regular, 0.7);
|
||||
|
||||
// this->messages.backgrounds.resub
|
||||
// this->messages.backgrounds.whisper
|
||||
this->messages.disabled = getColor(0, sat, 1, 0.6);
|
||||
// this->messages.seperator =
|
||||
// this->messages.seperatorInner =
|
||||
|
||||
// Scrollbar
|
||||
this->scrollbars.background = QColor(0, 0, 0, 0);
|
||||
// this->scrollbars.background = splits.background;
|
||||
// this->scrollbars.background.setAlphaF(qreal(0.2));
|
||||
this->scrollbars.thumb = getColor(0, sat, 0.70);
|
||||
this->scrollbars.thumbSelected = getColor(0, sat, 0.65);
|
||||
|
||||
// tooltip
|
||||
this->tooltip.background = QColor(0, 0, 0);
|
||||
this->tooltip.text = QColor(255, 255, 255);
|
||||
|
||||
// Selection
|
||||
this->messages.selection =
|
||||
isLightTheme() ? QColor(0, 0, 0, 64) : QColor(255, 255, 255, 64);
|
||||
}
|
||||
|
||||
QColor AB_THEME_CLASS::blendColors(const QColor &color1, const QColor &color2,
|
||||
qreal ratio)
|
||||
{
|
||||
int r = int(color1.red() * (1 - ratio) + color2.red() * ratio);
|
||||
int g = int(color1.green() * (1 - ratio) + color2.green() * ratio);
|
||||
int b = int(color1.blue() * (1 - ratio) + color2.blue() * ratio);
|
||||
|
||||
return QColor(r, g, b, 255);
|
||||
}
|
||||
|
||||
#ifndef AB_CUSTOM_THEME
|
||||
Theme *getTheme()
|
||||
{
|
||||
static auto theme = [] {
|
||||
auto theme = new Theme();
|
||||
theme->update();
|
||||
return theme;
|
||||
}();
|
||||
|
||||
return theme;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
|
|
@ -1,117 +1,117 @@
|
|||
#ifndef AB_THEME_H
|
||||
#define AB_THEME_H
|
||||
|
||||
#include <QBrush>
|
||||
#include <QColor>
|
||||
#include <common/ChatterinoSetting.hpp>
|
||||
|
||||
#ifdef AB_CUSTOM_THEME
|
||||
# define AB_THEME_CLASS BaseTheme
|
||||
#else
|
||||
# define AB_THEME_CLASS Theme
|
||||
#endif
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
class Theme;
|
||||
|
||||
class AB_THEME_CLASS
|
||||
{
|
||||
public:
|
||||
bool isLightTheme() const;
|
||||
|
||||
struct TabColors {
|
||||
QColor text;
|
||||
struct {
|
||||
QBrush regular;
|
||||
QBrush hover;
|
||||
QBrush unfocused;
|
||||
} backgrounds;
|
||||
struct {
|
||||
QColor regular;
|
||||
QColor hover;
|
||||
QColor unfocused;
|
||||
} line;
|
||||
};
|
||||
|
||||
/// WINDOW
|
||||
struct {
|
||||
QColor background;
|
||||
QColor text;
|
||||
QColor borderUnfocused;
|
||||
QColor borderFocused;
|
||||
} window;
|
||||
|
||||
/// TABS
|
||||
struct {
|
||||
TabColors regular;
|
||||
TabColors newMessage;
|
||||
TabColors highlighted;
|
||||
TabColors selected;
|
||||
QColor border;
|
||||
QColor bottomLine;
|
||||
} tabs;
|
||||
|
||||
/// MESSAGES
|
||||
struct {
|
||||
struct {
|
||||
QColor regular;
|
||||
QColor caret;
|
||||
QColor link;
|
||||
QColor system;
|
||||
} textColors;
|
||||
|
||||
struct {
|
||||
QColor regular;
|
||||
QColor alternate;
|
||||
QColor highlighted;
|
||||
QColor subscription;
|
||||
// QColor whisper;
|
||||
} backgrounds;
|
||||
|
||||
QColor disabled;
|
||||
// QColor seperator;
|
||||
// QColor seperatorInner;
|
||||
QColor selection;
|
||||
} messages;
|
||||
|
||||
/// SCROLLBAR
|
||||
struct {
|
||||
QColor background;
|
||||
QColor thumb;
|
||||
QColor thumbSelected;
|
||||
struct {
|
||||
QColor highlight;
|
||||
QColor subscription;
|
||||
} highlights;
|
||||
} scrollbars;
|
||||
|
||||
/// TOOLTIP
|
||||
struct {
|
||||
QColor text;
|
||||
QColor background;
|
||||
} tooltip;
|
||||
|
||||
void update();
|
||||
virtual void actuallyUpdate(double hue, double multiplier);
|
||||
QColor blendColors(const QColor &color1, const QColor &color2, qreal ratio);
|
||||
|
||||
pajlada::Signals::NoArgSignal updated;
|
||||
|
||||
QStringSetting themeName{"/appearance/theme/name", "Dark"};
|
||||
DoubleSetting themeHue{"/appearance/theme/hue", 0.0};
|
||||
|
||||
private:
|
||||
bool isLight_ = false;
|
||||
};
|
||||
|
||||
// Implemented in parent project if AB_CUSTOM_THEME is set.
|
||||
// Otherwise implemented in BaseThemecpp
|
||||
Theme *getTheme();
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
||||
#ifdef CHATTERINO
|
||||
# include "singletons/Theme.hpp"
|
||||
#endif
|
||||
#endif
|
||||
#ifndef AB_THEME_H
|
||||
#define AB_THEME_H
|
||||
|
||||
#include <QBrush>
|
||||
#include <QColor>
|
||||
#include <common/ChatterinoSetting.hpp>
|
||||
|
||||
#ifdef AB_CUSTOM_THEME
|
||||
# define AB_THEME_CLASS BaseTheme
|
||||
#else
|
||||
# define AB_THEME_CLASS Theme
|
||||
#endif
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
class Theme;
|
||||
|
||||
class AB_THEME_CLASS
|
||||
{
|
||||
public:
|
||||
bool isLightTheme() const;
|
||||
|
||||
struct TabColors {
|
||||
QColor text;
|
||||
struct {
|
||||
QBrush regular;
|
||||
QBrush hover;
|
||||
QBrush unfocused;
|
||||
} backgrounds;
|
||||
struct {
|
||||
QColor regular;
|
||||
QColor hover;
|
||||
QColor unfocused;
|
||||
} line;
|
||||
};
|
||||
|
||||
/// WINDOW
|
||||
struct {
|
||||
QColor background;
|
||||
QColor text;
|
||||
QColor borderUnfocused;
|
||||
QColor borderFocused;
|
||||
} window;
|
||||
|
||||
/// TABS
|
||||
struct {
|
||||
TabColors regular;
|
||||
TabColors newMessage;
|
||||
TabColors highlighted;
|
||||
TabColors selected;
|
||||
QColor border;
|
||||
QColor bottomLine;
|
||||
} tabs;
|
||||
|
||||
/// MESSAGES
|
||||
struct {
|
||||
struct {
|
||||
QColor regular;
|
||||
QColor caret;
|
||||
QColor link;
|
||||
QColor system;
|
||||
} textColors;
|
||||
|
||||
struct {
|
||||
QColor regular;
|
||||
QColor alternate;
|
||||
QColor highlighted;
|
||||
QColor subscription;
|
||||
// QColor whisper;
|
||||
} backgrounds;
|
||||
|
||||
QColor disabled;
|
||||
// QColor seperator;
|
||||
// QColor seperatorInner;
|
||||
QColor selection;
|
||||
} messages;
|
||||
|
||||
/// SCROLLBAR
|
||||
struct {
|
||||
QColor background;
|
||||
QColor thumb;
|
||||
QColor thumbSelected;
|
||||
struct {
|
||||
QColor highlight;
|
||||
QColor subscription;
|
||||
} highlights;
|
||||
} scrollbars;
|
||||
|
||||
/// TOOLTIP
|
||||
struct {
|
||||
QColor text;
|
||||
QColor background;
|
||||
} tooltip;
|
||||
|
||||
void update();
|
||||
virtual void actuallyUpdate(double hue, double multiplier);
|
||||
QColor blendColors(const QColor &color1, const QColor &color2, qreal ratio);
|
||||
|
||||
pajlada::Signals::NoArgSignal updated;
|
||||
|
||||
QStringSetting themeName{"/appearance/theme/name", "Dark"};
|
||||
DoubleSetting themeHue{"/appearance/theme/hue", 0.0};
|
||||
|
||||
private:
|
||||
bool isLight_ = false;
|
||||
};
|
||||
|
||||
// Implemented in parent project if AB_CUSTOM_THEME is set.
|
||||
// Otherwise implemented in BaseThemecpp
|
||||
Theme *getTheme();
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
||||
#ifdef CHATTERINO
|
||||
# include "singletons/Theme.hpp"
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -1,54 +1,54 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <pajlada/settings.hpp>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
void _registerSetting(std::weak_ptr<pajlada::Settings::SettingData> setting);
|
||||
|
||||
template <typename Type>
|
||||
class ChatterinoSetting : public pajlada::Settings::Setting<Type>
|
||||
{
|
||||
public:
|
||||
ChatterinoSetting(const std::string &path)
|
||||
: pajlada::Settings::Setting<Type>(path)
|
||||
{
|
||||
_registerSetting(this->getData());
|
||||
}
|
||||
|
||||
ChatterinoSetting(const std::string &path, const Type &defaultValue)
|
||||
: pajlada::Settings::Setting<Type>(path, defaultValue)
|
||||
{
|
||||
_registerSetting(this->getData());
|
||||
}
|
||||
|
||||
template <typename T2>
|
||||
ChatterinoSetting &operator=(const T2 &newValue)
|
||||
{
|
||||
this->setValue(newValue);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ChatterinoSetting &operator=(Type &&newValue) noexcept
|
||||
{
|
||||
pajlada::Settings::Setting<Type>::operator=(newValue);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
using pajlada::Settings::Setting<Type>::operator==;
|
||||
using pajlada::Settings::Setting<Type>::operator!=;
|
||||
|
||||
using pajlada::Settings::Setting<Type>::operator Type;
|
||||
};
|
||||
|
||||
using BoolSetting = ChatterinoSetting<bool>;
|
||||
using FloatSetting = ChatterinoSetting<float>;
|
||||
using DoubleSetting = ChatterinoSetting<double>;
|
||||
using IntSetting = ChatterinoSetting<int>;
|
||||
using StringSetting = ChatterinoSetting<std::string>;
|
||||
using QStringSetting = ChatterinoSetting<QString>;
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <pajlada/settings.hpp>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
void _registerSetting(std::weak_ptr<pajlada::Settings::SettingData> setting);
|
||||
|
||||
template <typename Type>
|
||||
class ChatterinoSetting : public pajlada::Settings::Setting<Type>
|
||||
{
|
||||
public:
|
||||
ChatterinoSetting(const std::string &path)
|
||||
: pajlada::Settings::Setting<Type>(path)
|
||||
{
|
||||
_registerSetting(this->getData());
|
||||
}
|
||||
|
||||
ChatterinoSetting(const std::string &path, const Type &defaultValue)
|
||||
: pajlada::Settings::Setting<Type>(path, defaultValue)
|
||||
{
|
||||
_registerSetting(this->getData());
|
||||
}
|
||||
|
||||
template <typename T2>
|
||||
ChatterinoSetting &operator=(const T2 &newValue)
|
||||
{
|
||||
this->setValue(newValue);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ChatterinoSetting &operator=(Type &&newValue) noexcept
|
||||
{
|
||||
pajlada::Settings::Setting<Type>::operator=(newValue);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
using pajlada::Settings::Setting<Type>::operator==;
|
||||
using pajlada::Settings::Setting<Type>::operator!=;
|
||||
|
||||
using pajlada::Settings::Setting<Type>::operator Type;
|
||||
};
|
||||
|
||||
using BoolSetting = ChatterinoSetting<bool>;
|
||||
using FloatSetting = ChatterinoSetting<float>;
|
||||
using DoubleSetting = ChatterinoSetting<double>;
|
||||
using IntSetting = ChatterinoSetting<int>;
|
||||
using StringSetting = ChatterinoSetting<std::string>;
|
||||
using QStringSetting = ChatterinoSetting<QString>;
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
|
|
@ -1,82 +1,82 @@
|
|||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename T, typename Q = typename std::underlying_type<T>::type>
|
||||
class FlagsEnum
|
||||
{
|
||||
public:
|
||||
FlagsEnum()
|
||||
: value_(static_cast<T>(0))
|
||||
{
|
||||
}
|
||||
|
||||
FlagsEnum(T value)
|
||||
: value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
FlagsEnum(std::initializer_list<T> flags)
|
||||
{
|
||||
for (auto flag : flags)
|
||||
{
|
||||
this->set(flag);
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const FlagsEnum<T> &other)
|
||||
{
|
||||
return this->value_ == other.value_;
|
||||
}
|
||||
|
||||
bool operator!=(const FlagsEnum &other)
|
||||
{
|
||||
return this->value_ != other.value_;
|
||||
}
|
||||
|
||||
void set(T flag)
|
||||
{
|
||||
reinterpret_cast<Q &>(this->value_) |= static_cast<Q>(flag);
|
||||
}
|
||||
|
||||
void unset(T flag)
|
||||
{
|
||||
reinterpret_cast<Q &>(this->value_) &= ~static_cast<Q>(flag);
|
||||
}
|
||||
|
||||
void set(T flag, bool value)
|
||||
{
|
||||
if (value)
|
||||
this->set(flag);
|
||||
else
|
||||
this->unset(flag);
|
||||
}
|
||||
|
||||
bool has(T flag) const
|
||||
{
|
||||
return static_cast<Q>(this->value_) & static_cast<Q>(flag);
|
||||
}
|
||||
|
||||
bool hasAny(FlagsEnum flags) const
|
||||
{
|
||||
return static_cast<Q>(this->value_) & static_cast<Q>(flags.value_);
|
||||
}
|
||||
|
||||
bool hasAll(FlagsEnum<T> flags) const
|
||||
{
|
||||
return (static_cast<Q>(this->value_) & static_cast<Q>(flags.value_)) &&
|
||||
static_cast<Q>(flags->value);
|
||||
}
|
||||
|
||||
bool hasNone(std::initializer_list<T> flags) const
|
||||
{
|
||||
return !this->hasAny(flags);
|
||||
}
|
||||
|
||||
private:
|
||||
T value_{};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename T, typename Q = typename std::underlying_type<T>::type>
|
||||
class FlagsEnum
|
||||
{
|
||||
public:
|
||||
FlagsEnum()
|
||||
: value_(static_cast<T>(0))
|
||||
{
|
||||
}
|
||||
|
||||
FlagsEnum(T value)
|
||||
: value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
FlagsEnum(std::initializer_list<T> flags)
|
||||
{
|
||||
for (auto flag : flags)
|
||||
{
|
||||
this->set(flag);
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const FlagsEnum<T> &other)
|
||||
{
|
||||
return this->value_ == other.value_;
|
||||
}
|
||||
|
||||
bool operator!=(const FlagsEnum &other)
|
||||
{
|
||||
return this->value_ != other.value_;
|
||||
}
|
||||
|
||||
void set(T flag)
|
||||
{
|
||||
reinterpret_cast<Q &>(this->value_) |= static_cast<Q>(flag);
|
||||
}
|
||||
|
||||
void unset(T flag)
|
||||
{
|
||||
reinterpret_cast<Q &>(this->value_) &= ~static_cast<Q>(flag);
|
||||
}
|
||||
|
||||
void set(T flag, bool value)
|
||||
{
|
||||
if (value)
|
||||
this->set(flag);
|
||||
else
|
||||
this->unset(flag);
|
||||
}
|
||||
|
||||
bool has(T flag) const
|
||||
{
|
||||
return static_cast<Q>(this->value_) & static_cast<Q>(flag);
|
||||
}
|
||||
|
||||
bool hasAny(FlagsEnum flags) const
|
||||
{
|
||||
return static_cast<Q>(this->value_) & static_cast<Q>(flags.value_);
|
||||
}
|
||||
|
||||
bool hasAll(FlagsEnum<T> flags) const
|
||||
{
|
||||
return (static_cast<Q>(this->value_) & static_cast<Q>(flags.value_)) &&
|
||||
static_cast<Q>(flags->value);
|
||||
}
|
||||
|
||||
bool hasNone(std::initializer_list<T> flags) const
|
||||
{
|
||||
return !this->hasAny(flags);
|
||||
}
|
||||
|
||||
private:
|
||||
T value_{};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
class Settings;
|
||||
class Paths;
|
||||
|
||||
class Singleton : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
virtual ~Singleton() = default;
|
||||
|
||||
virtual void initialize(Settings &settings, Paths &paths)
|
||||
{
|
||||
(void)(settings);
|
||||
(void)(paths);
|
||||
}
|
||||
|
||||
virtual void save()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
#pragma once
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
class Settings;
|
||||
class Paths;
|
||||
|
||||
class Singleton : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
virtual ~Singleton() = default;
|
||||
|
||||
virtual void initialize(Settings &settings, Paths &paths)
|
||||
{
|
||||
(void)(settings);
|
||||
(void)(paths);
|
||||
}
|
||||
|
||||
virtual void save()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QThread>
|
||||
#include <cassert>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
static bool isGuiThread()
|
||||
{
|
||||
return QCoreApplication::instance()->thread() == QThread::currentThread();
|
||||
}
|
||||
|
||||
static void assertInGuiThread()
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
assert(isGuiThread());
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
#pragma once
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QThread>
|
||||
#include <cassert>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
static bool isGuiThread()
|
||||
{
|
||||
return QCoreApplication::instance()->thread() == QThread::currentThread();
|
||||
}
|
||||
|
||||
static void assertInGuiThread()
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
assert(isGuiThread());
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
#include "Benchmark.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
BenchmarkGuard::BenchmarkGuard(const QString &_name)
|
||||
: name_(_name)
|
||||
{
|
||||
timer_.start();
|
||||
}
|
||||
|
||||
BenchmarkGuard::~BenchmarkGuard()
|
||||
{
|
||||
log("{} {} ms", this->name_, float(timer_.nsecsElapsed()) / 1000000.0f);
|
||||
}
|
||||
|
||||
qreal BenchmarkGuard::getElapsedMs()
|
||||
{
|
||||
return qreal(timer_.nsecsElapsed()) / 1000000.0;
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
#include "Benchmark.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
BenchmarkGuard::BenchmarkGuard(const QString &_name)
|
||||
: name_(_name)
|
||||
{
|
||||
timer_.start();
|
||||
}
|
||||
|
||||
BenchmarkGuard::~BenchmarkGuard()
|
||||
{
|
||||
log("{} {} ms", this->name_, float(timer_.nsecsElapsed()) / 1000000.0f);
|
||||
}
|
||||
|
||||
qreal BenchmarkGuard::getElapsedMs()
|
||||
{
|
||||
return qreal(timer_.nsecsElapsed()) / 1000000.0;
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "ABSettings.hpp"
|
||||
#include "ABTheme.hpp"
|
||||
#include "singletons/Fonts.hpp"
|
||||
#include "widgets/BaseWindow.hpp"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
using namespace AB_NAMESPACE;
|
||||
|
||||
QApplication a(argc, argv);
|
||||
|
||||
auto path =
|
||||
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
qDebug() << path;
|
||||
|
||||
QDir(path).mkdir(".");
|
||||
|
||||
new Settings(path);
|
||||
new Fonts();
|
||||
|
||||
BaseWindow widget(nullptr, BaseWindow::EnableCustomFrame);
|
||||
widget.setWindowTitle("asdf");
|
||||
widget.show();
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "ABSettings.hpp"
|
||||
#include "ABTheme.hpp"
|
||||
#include "singletons/Fonts.hpp"
|
||||
#include "widgets/BaseWindow.hpp"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
using namespace AB_NAMESPACE;
|
||||
|
||||
QApplication a(argc, argv);
|
||||
|
||||
auto path =
|
||||
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
qDebug() << path;
|
||||
|
||||
QDir(path).mkdir(".");
|
||||
|
||||
new Settings(path);
|
||||
new Fonts();
|
||||
|
||||
BaseWindow widget(nullptr, BaseWindow::EnableCustomFrame);
|
||||
widget.setWindowTitle("asdf");
|
||||
widget.show();
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
|
|
|
@ -1,100 +1,100 @@
|
|||
#-------------------------------------------------
|
||||
#
|
||||
# Project created by QtCreator 2018-11-19T19:03:22
|
||||
#
|
||||
#-------------------------------------------------
|
||||
|
||||
!AB_NOT_STANDALONE {
|
||||
message(appbase standalone)
|
||||
QT += core gui widgets
|
||||
TARGET = main
|
||||
TEMPLATE = app
|
||||
SOURCES += main.cpp
|
||||
|
||||
# https://bugreports.qt.io/browse/QTBUG-27018
|
||||
equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
|
||||
TARGET = bin/appbase
|
||||
}
|
||||
}
|
||||
|
||||
#DEFINES += QT_DEPRECATED_WARNINGS
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000
|
||||
|
||||
macx {
|
||||
# osx (Tested on macOS Mojave and High Sierra)
|
||||
CONFIG += c++17
|
||||
} else {
|
||||
CONFIG += c++17
|
||||
win32-msvc* {
|
||||
# win32 msvc
|
||||
QMAKE_CXXFLAGS += /std:c++17
|
||||
} else {
|
||||
# clang/gcc on linux or win32
|
||||
QMAKE_CXXFLAGS += -std=c++17
|
||||
}
|
||||
}
|
||||
|
||||
debug {
|
||||
DEFINES += QT_DEBUG
|
||||
}
|
||||
|
||||
linux {
|
||||
LIBS += -lrt
|
||||
QMAKE_LFLAGS += -lrt
|
||||
}
|
||||
|
||||
macx {
|
||||
INCLUDEPATH += /usr/local/include
|
||||
INCLUDEPATH += /usr/local/opt/openssl/include
|
||||
LIBS += -L/usr/local/opt/openssl/lib
|
||||
}
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/BaseSettings.cpp \
|
||||
$$PWD/BaseTheme.cpp \
|
||||
$$PWD/common/ChatterinoSetting.cpp \
|
||||
$$PWD/debug/Benchmark.cpp \
|
||||
$$PWD/singletons/Fonts.cpp \
|
||||
$$PWD/util/FunctionEventFilter.cpp \
|
||||
$$PWD/util/FuzzyConvert.cpp \
|
||||
$$PWD/util/Helpers.cpp \
|
||||
$$PWD/util/WindowsHelper.cpp \
|
||||
$$PWD/widgets/BaseWidget.cpp \
|
||||
$$PWD/widgets/BaseWindow.cpp \
|
||||
$$PWD/widgets/Label.cpp \
|
||||
$$PWD/widgets/TooltipWidget.cpp \
|
||||
$$PWD/widgets/helper/Button.cpp \
|
||||
$$PWD/widgets/helper/EffectLabel.cpp \
|
||||
$$PWD/widgets/helper/SignalLabel.cpp \
|
||||
$$PWD/widgets/helper/TitlebarButton.cpp \
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/BaseSettings.hpp \
|
||||
$$PWD/BaseTheme.hpp \
|
||||
$$PWD/common/ChatterinoSetting.hpp \
|
||||
$$PWD/common/FlagsEnum.hpp \
|
||||
$$PWD/common/Outcome.hpp \
|
||||
$$PWD/common/Singleton.hpp \
|
||||
$$PWD/debug/AssertInGuiThread.hpp \
|
||||
$$PWD/debug/Benchmark.hpp \
|
||||
$$PWD/debug/Log.hpp \
|
||||
$$PWD/singletons/Fonts.hpp \
|
||||
$$PWD/util/Clamp.hpp \
|
||||
$$PWD/util/CombinePath.hpp \
|
||||
$$PWD/util/DistanceBetweenPoints.hpp \
|
||||
$$PWD/util/FunctionEventFilter.hpp \
|
||||
$$PWD/util/FuzzyConvert.hpp \
|
||||
$$PWD/util/Helpers.hpp \
|
||||
$$PWD/util/LayoutHelper.hpp \
|
||||
$$PWD/util/PostToThread.hpp \
|
||||
$$PWD/util/RapidJsonSerializeQString.hpp \
|
||||
$$PWD/util/Shortcut.hpp \
|
||||
$$PWD/util/WindowsHelper.hpp \
|
||||
$$PWD/widgets/BaseWidget.hpp \
|
||||
$$PWD/widgets/BaseWindow.hpp \
|
||||
$$PWD/widgets/Label.hpp \
|
||||
$$PWD/widgets/TooltipWidget.hpp \
|
||||
$$PWD/widgets/helper/Button.hpp \
|
||||
$$PWD/widgets/helper/EffectLabel.hpp \
|
||||
$$PWD/widgets/helper/SignalLabel.hpp \
|
||||
$$PWD/widgets/helper/TitlebarButton.hpp \
|
||||
#-------------------------------------------------
|
||||
#
|
||||
# Project created by QtCreator 2018-11-19T19:03:22
|
||||
#
|
||||
#-------------------------------------------------
|
||||
|
||||
!AB_NOT_STANDALONE {
|
||||
message(appbase standalone)
|
||||
QT += core gui widgets
|
||||
TARGET = main
|
||||
TEMPLATE = app
|
||||
SOURCES += main.cpp
|
||||
|
||||
# https://bugreports.qt.io/browse/QTBUG-27018
|
||||
equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
|
||||
TARGET = bin/appbase
|
||||
}
|
||||
}
|
||||
|
||||
#DEFINES += QT_DEPRECATED_WARNINGS
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000
|
||||
|
||||
macx {
|
||||
# osx (Tested on macOS Mojave and High Sierra)
|
||||
CONFIG += c++17
|
||||
} else {
|
||||
CONFIG += c++17
|
||||
win32-msvc* {
|
||||
# win32 msvc
|
||||
QMAKE_CXXFLAGS += /std:c++17
|
||||
} else {
|
||||
# clang/gcc on linux or win32
|
||||
QMAKE_CXXFLAGS += -std=c++17
|
||||
}
|
||||
}
|
||||
|
||||
debug {
|
||||
DEFINES += QT_DEBUG
|
||||
}
|
||||
|
||||
linux {
|
||||
LIBS += -lrt
|
||||
QMAKE_LFLAGS += -lrt
|
||||
}
|
||||
|
||||
macx {
|
||||
INCLUDEPATH += /usr/local/include
|
||||
INCLUDEPATH += /usr/local/opt/openssl/include
|
||||
LIBS += -L/usr/local/opt/openssl/lib
|
||||
}
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/BaseSettings.cpp \
|
||||
$$PWD/BaseTheme.cpp \
|
||||
$$PWD/common/ChatterinoSetting.cpp \
|
||||
$$PWD/debug/Benchmark.cpp \
|
||||
$$PWD/singletons/Fonts.cpp \
|
||||
$$PWD/util/FunctionEventFilter.cpp \
|
||||
$$PWD/util/FuzzyConvert.cpp \
|
||||
$$PWD/util/Helpers.cpp \
|
||||
$$PWD/util/WindowsHelper.cpp \
|
||||
$$PWD/widgets/BaseWidget.cpp \
|
||||
$$PWD/widgets/BaseWindow.cpp \
|
||||
$$PWD/widgets/Label.cpp \
|
||||
$$PWD/widgets/TooltipWidget.cpp \
|
||||
$$PWD/widgets/helper/Button.cpp \
|
||||
$$PWD/widgets/helper/EffectLabel.cpp \
|
||||
$$PWD/widgets/helper/SignalLabel.cpp \
|
||||
$$PWD/widgets/helper/TitlebarButton.cpp \
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/BaseSettings.hpp \
|
||||
$$PWD/BaseTheme.hpp \
|
||||
$$PWD/common/ChatterinoSetting.hpp \
|
||||
$$PWD/common/FlagsEnum.hpp \
|
||||
$$PWD/common/Outcome.hpp \
|
||||
$$PWD/common/Singleton.hpp \
|
||||
$$PWD/debug/AssertInGuiThread.hpp \
|
||||
$$PWD/debug/Benchmark.hpp \
|
||||
$$PWD/debug/Log.hpp \
|
||||
$$PWD/singletons/Fonts.hpp \
|
||||
$$PWD/util/Clamp.hpp \
|
||||
$$PWD/util/CombinePath.hpp \
|
||||
$$PWD/util/DistanceBetweenPoints.hpp \
|
||||
$$PWD/util/FunctionEventFilter.hpp \
|
||||
$$PWD/util/FuzzyConvert.hpp \
|
||||
$$PWD/util/Helpers.hpp \
|
||||
$$PWD/util/LayoutHelper.hpp \
|
||||
$$PWD/util/PostToThread.hpp \
|
||||
$$PWD/util/RapidJsonSerializeQString.hpp \
|
||||
$$PWD/util/Shortcut.hpp \
|
||||
$$PWD/util/WindowsHelper.hpp \
|
||||
$$PWD/widgets/BaseWidget.hpp \
|
||||
$$PWD/widgets/BaseWindow.hpp \
|
||||
$$PWD/widgets/Label.hpp \
|
||||
$$PWD/widgets/TooltipWidget.hpp \
|
||||
$$PWD/widgets/helper/Button.hpp \
|
||||
$$PWD/widgets/helper/EffectLabel.hpp \
|
||||
$$PWD/widgets/helper/SignalLabel.hpp \
|
||||
$$PWD/widgets/helper/TitlebarButton.hpp \
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
// http://en.cppreference.com/w/cpp/algorithm/clamp
|
||||
|
||||
template <class T>
|
||||
constexpr const T &clamp(const T &v, const T &lo, const T &hi)
|
||||
{
|
||||
return assert(!(hi < lo)), (v < lo) ? lo : (hi < v) ? hi : v;
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
#pragma once
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
// http://en.cppreference.com/w/cpp/algorithm/clamp
|
||||
|
||||
template <class T>
|
||||
constexpr const T &clamp(const T &v, const T &lo, const T &hi)
|
||||
{
|
||||
return assert(!(hi < lo)), (v < lo) ? lo : (hi < v) ? hi : v;
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// https://stackoverflow.com/a/13014491
|
||||
inline QString combinePath(const QString &a, const QString &b)
|
||||
{
|
||||
return QDir::cleanPath(a + QDir::separator() + b);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// https://stackoverflow.com/a/13014491
|
||||
inline QString combinePath(const QString &a, const QString &b)
|
||||
{
|
||||
return QDir::cleanPath(a + QDir::separator() + b);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
#include "FunctionEventFilter.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
FunctionEventFilter::FunctionEventFilter(
|
||||
QObject *parent, std::function<bool(QObject *, QEvent *)> function)
|
||||
: QObject(parent)
|
||||
, function_(std::move(function))
|
||||
{
|
||||
}
|
||||
|
||||
bool FunctionEventFilter::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
return this->function_(watched, event);
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
#include "FunctionEventFilter.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
FunctionEventFilter::FunctionEventFilter(
|
||||
QObject *parent, std::function<bool(QObject *, QEvent *)> function)
|
||||
: QObject(parent)
|
||||
, function_(std::move(function))
|
||||
{
|
||||
}
|
||||
|
||||
bool FunctionEventFilter::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
return this->function_(watched, event);
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <QEvent>
|
||||
#include <QObject>
|
||||
#include <functional>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
class FunctionEventFilter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FunctionEventFilter(QObject *parent,
|
||||
std::function<bool(QObject *, QEvent *)> function);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
private:
|
||||
std::function<bool(QObject *, QEvent *)> function_;
|
||||
};
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
#pragma once
|
||||
|
||||
#include <QEvent>
|
||||
#include <QObject>
|
||||
#include <functional>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
class FunctionEventFilter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FunctionEventFilter(QObject *parent,
|
||||
std::function<bool(QObject *, QEvent *)> function);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
private:
|
||||
std::function<bool(QObject *, QEvent *)> function_;
|
||||
};
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
#include "FuzzyConvert.hpp"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
int fuzzyToInt(const QString &str, int default_)
|
||||
{
|
||||
static auto intFinder = QRegularExpression("[0-9]+");
|
||||
|
||||
auto match = intFinder.match(str);
|
||||
if (match.hasMatch())
|
||||
{
|
||||
return match.captured().toInt();
|
||||
}
|
||||
|
||||
return default_;
|
||||
}
|
||||
|
||||
float fuzzyToFloat(const QString &str, float default_)
|
||||
{
|
||||
static auto floatFinder = QRegularExpression("[0-9]+(\\.[0-9]+)?");
|
||||
|
||||
auto match = floatFinder.match(str);
|
||||
if (match.hasMatch())
|
||||
{
|
||||
return match.captured().toFloat();
|
||||
}
|
||||
|
||||
return default_;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "FuzzyConvert.hpp"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
int fuzzyToInt(const QString &str, int default_)
|
||||
{
|
||||
static auto intFinder = QRegularExpression("[0-9]+");
|
||||
|
||||
auto match = intFinder.match(str);
|
||||
if (match.hasMatch())
|
||||
{
|
||||
return match.captured().toInt();
|
||||
}
|
||||
|
||||
return default_;
|
||||
}
|
||||
|
||||
float fuzzyToFloat(const QString &str, float default_)
|
||||
{
|
||||
static auto floatFinder = QRegularExpression("[0-9]+(\\.[0-9]+)?");
|
||||
|
||||
auto match = floatFinder.match(str);
|
||||
if (match.hasMatch())
|
||||
{
|
||||
return match.captured().toFloat();
|
||||
}
|
||||
|
||||
return default_;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
int fuzzyToInt(const QString &str, int default_);
|
||||
float fuzzyToFloat(const QString &str, float default_);
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
int fuzzyToInt(const QString &str, int default_);
|
||||
float fuzzyToFloat(const QString &str, float default_);
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,42 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include <QLayout>
|
||||
#include <QWidget>
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
using LayoutItem = boost::variant<QWidget *, QLayout *>;
|
||||
|
||||
template <typename T>
|
||||
T *makeLayout(std::initializer_list<LayoutItem> items)
|
||||
{
|
||||
auto t = new T;
|
||||
|
||||
for (auto &item : items)
|
||||
{
|
||||
switch (item.which())
|
||||
{
|
||||
case 0:
|
||||
t->addItem(new QWidgetItem(boost::get<QWidget *>(item)));
|
||||
break;
|
||||
case 1:
|
||||
t->addItem(boost::get<QLayout *>(item));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
template <typename T, typename With>
|
||||
T *makeWidget(With with)
|
||||
{
|
||||
auto t = new T;
|
||||
|
||||
with(t);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QLayout>
|
||||
#include <QWidget>
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
using LayoutItem = boost::variant<QWidget *, QLayout *>;
|
||||
|
||||
template <typename T>
|
||||
T *makeLayout(std::initializer_list<LayoutItem> items)
|
||||
{
|
||||
auto t = new T;
|
||||
|
||||
for (auto &item : items)
|
||||
{
|
||||
switch (item.which())
|
||||
{
|
||||
case 0:
|
||||
t->addItem(new QWidgetItem(boost::get<QWidget *>(item)));
|
||||
break;
|
||||
case 1:
|
||||
t->addItem(boost::get<QLayout *>(item));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
template <typename T, typename With>
|
||||
T *makeWidget(With with)
|
||||
{
|
||||
auto t = new T;
|
||||
|
||||
with(t);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,86 +1,86 @@
|
|||
#include "WindowsHelper.hpp"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
#ifdef USEWINSDK
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
typedef enum MONITOR_DPI_TYPE {
|
||||
MDT_EFFECTIVE_DPI = 0,
|
||||
MDT_ANGULAR_DPI = 1,
|
||||
MDT_RAW_DPI = 2,
|
||||
MDT_DEFAULT = MDT_EFFECTIVE_DPI
|
||||
} MONITOR_DPI_TYPE;
|
||||
|
||||
typedef HRESULT(CALLBACK *GetDpiForMonitor_)(HMONITOR, MONITOR_DPI_TYPE, UINT *,
|
||||
UINT *);
|
||||
|
||||
boost::optional<UINT> getWindowDpi(HWND hwnd)
|
||||
{
|
||||
static HINSTANCE shcore = LoadLibrary(L"Shcore.dll");
|
||||
if (shcore != nullptr)
|
||||
{
|
||||
if (auto getDpiForMonitor =
|
||||
GetDpiForMonitor_(GetProcAddress(shcore, "GetDpiForMonitor")))
|
||||
{
|
||||
HMONITOR monitor =
|
||||
MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||
|
||||
UINT xScale, yScale;
|
||||
|
||||
getDpiForMonitor(monitor, MDT_DEFAULT, &xScale, &yScale);
|
||||
|
||||
return xScale;
|
||||
}
|
||||
}
|
||||
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
typedef HRESULT(CALLBACK *OleFlushClipboard_)();
|
||||
|
||||
void flushClipboard()
|
||||
{
|
||||
static HINSTANCE ole32 = LoadLibrary(L"Ole32.dll");
|
||||
if (ole32 != nullptr)
|
||||
{
|
||||
if (auto oleFlushClipboard =
|
||||
OleFlushClipboard_(GetProcAddress(ole32, "OleFlushClipboard")))
|
||||
{
|
||||
oleFlushClipboard();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr const char *runKey =
|
||||
"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
||||
|
||||
bool isRegisteredForStartup()
|
||||
{
|
||||
QSettings settings(runKey, QSettings::NativeFormat);
|
||||
|
||||
return !settings.value("Chatterino").toString().isEmpty();
|
||||
}
|
||||
|
||||
void setRegisteredForStartup(bool isRegistered)
|
||||
{
|
||||
QSettings settings(runKey, QSettings::NativeFormat);
|
||||
|
||||
if (isRegistered)
|
||||
{
|
||||
auto exePath = QFileInfo(QCoreApplication::applicationFilePath())
|
||||
.absoluteFilePath()
|
||||
.replace('/', '\\');
|
||||
|
||||
settings.setValue("Chatterino", "\"" + exePath + "\" --autorun");
|
||||
}
|
||||
else
|
||||
{
|
||||
settings.remove("Chatterino");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
||||
#endif
|
||||
#include "WindowsHelper.hpp"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
#ifdef USEWINSDK
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
typedef enum MONITOR_DPI_TYPE {
|
||||
MDT_EFFECTIVE_DPI = 0,
|
||||
MDT_ANGULAR_DPI = 1,
|
||||
MDT_RAW_DPI = 2,
|
||||
MDT_DEFAULT = MDT_EFFECTIVE_DPI
|
||||
} MONITOR_DPI_TYPE;
|
||||
|
||||
typedef HRESULT(CALLBACK *GetDpiForMonitor_)(HMONITOR, MONITOR_DPI_TYPE, UINT *,
|
||||
UINT *);
|
||||
|
||||
boost::optional<UINT> getWindowDpi(HWND hwnd)
|
||||
{
|
||||
static HINSTANCE shcore = LoadLibrary(L"Shcore.dll");
|
||||
if (shcore != nullptr)
|
||||
{
|
||||
if (auto getDpiForMonitor =
|
||||
GetDpiForMonitor_(GetProcAddress(shcore, "GetDpiForMonitor")))
|
||||
{
|
||||
HMONITOR monitor =
|
||||
MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||
|
||||
UINT xScale, yScale;
|
||||
|
||||
getDpiForMonitor(monitor, MDT_DEFAULT, &xScale, &yScale);
|
||||
|
||||
return xScale;
|
||||
}
|
||||
}
|
||||
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
typedef HRESULT(CALLBACK *OleFlushClipboard_)();
|
||||
|
||||
void flushClipboard()
|
||||
{
|
||||
static HINSTANCE ole32 = LoadLibrary(L"Ole32.dll");
|
||||
if (ole32 != nullptr)
|
||||
{
|
||||
if (auto oleFlushClipboard =
|
||||
OleFlushClipboard_(GetProcAddress(ole32, "OleFlushClipboard")))
|
||||
{
|
||||
oleFlushClipboard();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr const char *runKey =
|
||||
"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
||||
|
||||
bool isRegisteredForStartup()
|
||||
{
|
||||
QSettings settings(runKey, QSettings::NativeFormat);
|
||||
|
||||
return !settings.value("Chatterino").toString().isEmpty();
|
||||
}
|
||||
|
||||
void setRegisteredForStartup(bool isRegistered)
|
||||
{
|
||||
QSettings settings(runKey, QSettings::NativeFormat);
|
||||
|
||||
if (isRegistered)
|
||||
{
|
||||
auto exePath = QFileInfo(QCoreApplication::applicationFilePath())
|
||||
.absoluteFilePath()
|
||||
.replace('/', '\\');
|
||||
|
||||
settings.setValue("Chatterino", "\"" + exePath + "\" --autorun");
|
||||
}
|
||||
else
|
||||
{
|
||||
settings.remove("Chatterino");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USEWINSDK
|
||||
|
||||
# include <Windows.h>
|
||||
# include <boost/optional.hpp>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
boost::optional<UINT> getWindowDpi(HWND hwnd);
|
||||
void flushClipboard();
|
||||
|
||||
bool isRegisteredForStartup();
|
||||
void setRegisteredForStartup(bool isRegistered);
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
||||
#endif
|
||||
#pragma once
|
||||
|
||||
#ifdef USEWINSDK
|
||||
|
||||
# include <Windows.h>
|
||||
# include <boost/optional.hpp>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
boost::optional<UINT> getWindowDpi(HWND hwnd);
|
||||
void flushClipboard();
|
||||
|
||||
bool isRegisteredForStartup();
|
||||
void setRegisteredForStartup(bool isRegistered);
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,137 +1,137 @@
|
|||
#pragma once
|
||||
|
||||
#include "widgets/BaseWidget.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
|
||||
class QHBoxLayout;
|
||||
struct tagMSG;
|
||||
typedef struct tagMSG MSG;
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
class Button;
|
||||
class EffectLabel;
|
||||
class TitleBarButton;
|
||||
enum class TitleBarButtonStyle;
|
||||
|
||||
class BaseWindow : public BaseWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Flags {
|
||||
None = 0,
|
||||
EnableCustomFrame = 1,
|
||||
Frameless = 2,
|
||||
TopMost = 4,
|
||||
DisableCustomScaling = 8,
|
||||
FramelessDraggable = 16,
|
||||
};
|
||||
|
||||
enum ActionOnFocusLoss { Nothing, Delete, Close, Hide };
|
||||
|
||||
explicit BaseWindow(QWidget *parent = nullptr, Flags flags_ = None);
|
||||
|
||||
void setInitialBounds(const QRect &bounds);
|
||||
QRect getBounds();
|
||||
|
||||
QWidget *getLayoutContainer();
|
||||
bool hasCustomWindowFrame();
|
||||
TitleBarButton *addTitleBarButton(const TitleBarButtonStyle &style,
|
||||
std::function<void()> onClicked);
|
||||
EffectLabel *addTitleBarLabel(std::function<void()> onClicked);
|
||||
|
||||
void setStayInScreenRect(bool value);
|
||||
bool getStayInScreenRect() const;
|
||||
|
||||
void setActionOnFocusLoss(ActionOnFocusLoss value);
|
||||
ActionOnFocusLoss getActionOnFocusLoss() const;
|
||||
|
||||
void moveTo(QWidget *widget, QPoint point, bool offset = true);
|
||||
|
||||
virtual float scale() const override;
|
||||
float qtFontScale() const;
|
||||
|
||||
Flags getFlags();
|
||||
|
||||
pajlada::Signals::NoArgSignal closing;
|
||||
|
||||
static bool supportsCustomWindowFrame();
|
||||
|
||||
protected:
|
||||
virtual bool nativeEvent(const QByteArray &eventType, void *message,
|
||||
long *result) override;
|
||||
virtual void scaleChangedEvent(float) override;
|
||||
|
||||
virtual void paintEvent(QPaintEvent *) override;
|
||||
|
||||
virtual void changeEvent(QEvent *) override;
|
||||
virtual void leaveEvent(QEvent *) override;
|
||||
virtual void resizeEvent(QResizeEvent *) override;
|
||||
virtual void moveEvent(QMoveEvent *) override;
|
||||
virtual void closeEvent(QCloseEvent *) override;
|
||||
|
||||
virtual void themeChangedEvent() override;
|
||||
virtual bool event(QEvent *event) override;
|
||||
virtual void wheelEvent(QWheelEvent *event) override;
|
||||
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
QPointF movingRelativePos;
|
||||
bool moving{};
|
||||
|
||||
void updateScale();
|
||||
|
||||
boost::optional<QColor> overrideBackgroundColor_;
|
||||
|
||||
private:
|
||||
void init();
|
||||
void moveIntoDesktopRect(QWidget *parent);
|
||||
void calcButtonsSizes();
|
||||
void drawCustomWindowFrame(QPainter &painter);
|
||||
void onFocusLost();
|
||||
|
||||
bool handleDPICHANGED(MSG *msg);
|
||||
bool handleSHOWWINDOW(MSG *msg);
|
||||
bool handleNCCALCSIZE(MSG *msg, long *result);
|
||||
bool handleSIZE(MSG *msg);
|
||||
bool handleMOVE(MSG *msg);
|
||||
bool handleNCHITTEST(MSG *msg, long *result);
|
||||
|
||||
bool enableCustomFrame_;
|
||||
ActionOnFocusLoss actionOnFocusLoss_ = Nothing;
|
||||
bool frameless_;
|
||||
bool stayInScreenRect_ = false;
|
||||
bool shown_ = false;
|
||||
Flags flags_;
|
||||
float nativeScale_ = 1;
|
||||
|
||||
struct {
|
||||
QLayout *windowLayout = nullptr;
|
||||
QHBoxLayout *titlebarBox = nullptr;
|
||||
QWidget *titleLabel = nullptr;
|
||||
TitleBarButton *minButton = nullptr;
|
||||
TitleBarButton *maxButton = nullptr;
|
||||
TitleBarButton *exitButton = nullptr;
|
||||
QWidget *layoutBase = nullptr;
|
||||
std::vector<Button *> buttons;
|
||||
} ui_;
|
||||
|
||||
#ifdef USEWINSDK
|
||||
QRect initalBounds_;
|
||||
QRect currentBounds_;
|
||||
QRect nextBounds_;
|
||||
QTimer useNextBounds_;
|
||||
bool isNotMinimizedOrMaximized_{};
|
||||
#endif
|
||||
|
||||
pajlada::Signals::SignalHolder connections_;
|
||||
std::vector<pajlada::Signals::ScopedConnection> managedConnections_;
|
||||
|
||||
friend class BaseWidget;
|
||||
};
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
#pragma once
|
||||
|
||||
#include "widgets/BaseWidget.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
|
||||
class QHBoxLayout;
|
||||
struct tagMSG;
|
||||
typedef struct tagMSG MSG;
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
class Button;
|
||||
class EffectLabel;
|
||||
class TitleBarButton;
|
||||
enum class TitleBarButtonStyle;
|
||||
|
||||
class BaseWindow : public BaseWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Flags {
|
||||
None = 0,
|
||||
EnableCustomFrame = 1,
|
||||
Frameless = 2,
|
||||
TopMost = 4,
|
||||
DisableCustomScaling = 8,
|
||||
FramelessDraggable = 16,
|
||||
};
|
||||
|
||||
enum ActionOnFocusLoss { Nothing, Delete, Close, Hide };
|
||||
|
||||
explicit BaseWindow(QWidget *parent = nullptr, Flags flags_ = None);
|
||||
|
||||
void setInitialBounds(const QRect &bounds);
|
||||
QRect getBounds();
|
||||
|
||||
QWidget *getLayoutContainer();
|
||||
bool hasCustomWindowFrame();
|
||||
TitleBarButton *addTitleBarButton(const TitleBarButtonStyle &style,
|
||||
std::function<void()> onClicked);
|
||||
EffectLabel *addTitleBarLabel(std::function<void()> onClicked);
|
||||
|
||||
void setStayInScreenRect(bool value);
|
||||
bool getStayInScreenRect() const;
|
||||
|
||||
void setActionOnFocusLoss(ActionOnFocusLoss value);
|
||||
ActionOnFocusLoss getActionOnFocusLoss() const;
|
||||
|
||||
void moveTo(QWidget *widget, QPoint point, bool offset = true);
|
||||
|
||||
virtual float scale() const override;
|
||||
float qtFontScale() const;
|
||||
|
||||
Flags getFlags();
|
||||
|
||||
pajlada::Signals::NoArgSignal closing;
|
||||
|
||||
static bool supportsCustomWindowFrame();
|
||||
|
||||
protected:
|
||||
virtual bool nativeEvent(const QByteArray &eventType, void *message,
|
||||
long *result) override;
|
||||
virtual void scaleChangedEvent(float) override;
|
||||
|
||||
virtual void paintEvent(QPaintEvent *) override;
|
||||
|
||||
virtual void changeEvent(QEvent *) override;
|
||||
virtual void leaveEvent(QEvent *) override;
|
||||
virtual void resizeEvent(QResizeEvent *) override;
|
||||
virtual void moveEvent(QMoveEvent *) override;
|
||||
virtual void closeEvent(QCloseEvent *) override;
|
||||
|
||||
virtual void themeChangedEvent() override;
|
||||
virtual bool event(QEvent *event) override;
|
||||
virtual void wheelEvent(QWheelEvent *event) override;
|
||||
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
QPointF movingRelativePos;
|
||||
bool moving{};
|
||||
|
||||
void updateScale();
|
||||
|
||||
boost::optional<QColor> overrideBackgroundColor_;
|
||||
|
||||
private:
|
||||
void init();
|
||||
void moveIntoDesktopRect(QWidget *parent);
|
||||
void calcButtonsSizes();
|
||||
void drawCustomWindowFrame(QPainter &painter);
|
||||
void onFocusLost();
|
||||
|
||||
bool handleDPICHANGED(MSG *msg);
|
||||
bool handleSHOWWINDOW(MSG *msg);
|
||||
bool handleNCCALCSIZE(MSG *msg, long *result);
|
||||
bool handleSIZE(MSG *msg);
|
||||
bool handleMOVE(MSG *msg);
|
||||
bool handleNCHITTEST(MSG *msg, long *result);
|
||||
|
||||
bool enableCustomFrame_;
|
||||
ActionOnFocusLoss actionOnFocusLoss_ = Nothing;
|
||||
bool frameless_;
|
||||
bool stayInScreenRect_ = false;
|
||||
bool shown_ = false;
|
||||
Flags flags_;
|
||||
float nativeScale_ = 1;
|
||||
|
||||
struct {
|
||||
QLayout *windowLayout = nullptr;
|
||||
QHBoxLayout *titlebarBox = nullptr;
|
||||
QWidget *titleLabel = nullptr;
|
||||
TitleBarButton *minButton = nullptr;
|
||||
TitleBarButton *maxButton = nullptr;
|
||||
TitleBarButton *exitButton = nullptr;
|
||||
QWidget *layoutBase = nullptr;
|
||||
std::vector<Button *> buttons;
|
||||
} ui_;
|
||||
|
||||
#ifdef USEWINSDK
|
||||
QRect initalBounds_;
|
||||
QRect currentBounds_;
|
||||
QRect nextBounds_;
|
||||
QTimer useNextBounds_;
|
||||
bool isNotMinimizedOrMaximized_{};
|
||||
#endif
|
||||
|
||||
pajlada::Signals::SignalHolder connections_;
|
||||
std::vector<pajlada::Signals::ScopedConnection> managedConnections_;
|
||||
|
||||
friend class BaseWidget;
|
||||
};
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
|
|
@ -1,132 +1,132 @@
|
|||
#include "Label.hpp"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
Label::Label(QString text, FontStyle style)
|
||||
: Label(nullptr, text, style)
|
||||
{
|
||||
}
|
||||
|
||||
Label::Label(BaseWidget *parent, QString text, FontStyle style)
|
||||
: BaseWidget(parent)
|
||||
, text_(text)
|
||||
, fontStyle_(style)
|
||||
{
|
||||
this->connections_.managedConnect(getFonts()->fontChanged,
|
||||
[this] { this->updateSize(); });
|
||||
}
|
||||
|
||||
const QString &Label::getText() const
|
||||
{
|
||||
return this->text_;
|
||||
}
|
||||
|
||||
void Label::setText(const QString &text)
|
||||
{
|
||||
if (this->text_ != text)
|
||||
{
|
||||
this->text_ = text;
|
||||
this->updateSize();
|
||||
this->update();
|
||||
}
|
||||
}
|
||||
|
||||
FontStyle Label::getFontStyle() const
|
||||
{
|
||||
return this->fontStyle_;
|
||||
}
|
||||
|
||||
bool Label::getCentered() const
|
||||
{
|
||||
return this->centered_;
|
||||
}
|
||||
|
||||
void Label::setCentered(bool centered)
|
||||
{
|
||||
this->centered_ = centered;
|
||||
this->updateSize();
|
||||
}
|
||||
|
||||
bool Label::getHasOffset() const
|
||||
{
|
||||
return this->hasOffset_;
|
||||
}
|
||||
|
||||
void Label::setHasOffset(bool hasOffset)
|
||||
{
|
||||
this->hasOffset_ = hasOffset;
|
||||
this->updateSize();
|
||||
}
|
||||
void Label::setFontStyle(FontStyle style)
|
||||
{
|
||||
this->fontStyle_ = style;
|
||||
this->updateSize();
|
||||
}
|
||||
|
||||
void Label::scaleChangedEvent(float scale)
|
||||
{
|
||||
this->updateSize();
|
||||
}
|
||||
|
||||
QSize Label::sizeHint() const
|
||||
{
|
||||
return this->preferedSize_;
|
||||
}
|
||||
|
||||
QSize Label::minimumSizeHint() const
|
||||
{
|
||||
return this->preferedSize_;
|
||||
}
|
||||
|
||||
void Label::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter painter(this);
|
||||
QFontMetrics metrics = getFonts()->getFontMetrics(
|
||||
this->getFontStyle(),
|
||||
this->scale() * 96.f / this->logicalDpiX() * this->devicePixelRatioF());
|
||||
painter.setFont(getFonts()->getFont(
|
||||
this->getFontStyle(), this->scale() * 96.f / this->logicalDpiX() *
|
||||
this->devicePixelRatioF()));
|
||||
|
||||
int offset = this->getOffset();
|
||||
|
||||
// draw text
|
||||
QRect textRect(offset, 0, this->width() - offset - offset, this->height());
|
||||
|
||||
int width = metrics.width(this->text_);
|
||||
Qt::Alignment alignment = !this->centered_ || width > textRect.width()
|
||||
? Qt::AlignLeft | Qt::AlignVCenter
|
||||
: Qt::AlignCenter;
|
||||
|
||||
painter.setBrush(this->palette().windowText());
|
||||
|
||||
QTextOption option(alignment);
|
||||
option.setWrapMode(QTextOption::NoWrap);
|
||||
painter.drawText(textRect, this->text_, option);
|
||||
|
||||
#if 0
|
||||
painter.setPen(QColor(255, 0, 0));
|
||||
painter.drawRect(0, 0, this->width() - 1, this->height() - 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Label::updateSize()
|
||||
{
|
||||
QFontMetrics metrics =
|
||||
getFonts()->getFontMetrics(this->fontStyle_, this->scale());
|
||||
|
||||
int width = metrics.width(this->text_) + (2 * this->getOffset());
|
||||
int height = metrics.height();
|
||||
this->preferedSize_ = QSize(width, height);
|
||||
|
||||
this->updateGeometry();
|
||||
}
|
||||
|
||||
int Label::getOffset()
|
||||
{
|
||||
return this->hasOffset_ ? int(8 * this->scale()) : 0;
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
#include "Label.hpp"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
Label::Label(QString text, FontStyle style)
|
||||
: Label(nullptr, text, style)
|
||||
{
|
||||
}
|
||||
|
||||
Label::Label(BaseWidget *parent, QString text, FontStyle style)
|
||||
: BaseWidget(parent)
|
||||
, text_(text)
|
||||
, fontStyle_(style)
|
||||
{
|
||||
this->connections_.managedConnect(getFonts()->fontChanged,
|
||||
[this] { this->updateSize(); });
|
||||
}
|
||||
|
||||
const QString &Label::getText() const
|
||||
{
|
||||
return this->text_;
|
||||
}
|
||||
|
||||
void Label::setText(const QString &text)
|
||||
{
|
||||
if (this->text_ != text)
|
||||
{
|
||||
this->text_ = text;
|
||||
this->updateSize();
|
||||
this->update();
|
||||
}
|
||||
}
|
||||
|
||||
FontStyle Label::getFontStyle() const
|
||||
{
|
||||
return this->fontStyle_;
|
||||
}
|
||||
|
||||
bool Label::getCentered() const
|
||||
{
|
||||
return this->centered_;
|
||||
}
|
||||
|
||||
void Label::setCentered(bool centered)
|
||||
{
|
||||
this->centered_ = centered;
|
||||
this->updateSize();
|
||||
}
|
||||
|
||||
bool Label::getHasOffset() const
|
||||
{
|
||||
return this->hasOffset_;
|
||||
}
|
||||
|
||||
void Label::setHasOffset(bool hasOffset)
|
||||
{
|
||||
this->hasOffset_ = hasOffset;
|
||||
this->updateSize();
|
||||
}
|
||||
void Label::setFontStyle(FontStyle style)
|
||||
{
|
||||
this->fontStyle_ = style;
|
||||
this->updateSize();
|
||||
}
|
||||
|
||||
void Label::scaleChangedEvent(float scale)
|
||||
{
|
||||
this->updateSize();
|
||||
}
|
||||
|
||||
QSize Label::sizeHint() const
|
||||
{
|
||||
return this->preferedSize_;
|
||||
}
|
||||
|
||||
QSize Label::minimumSizeHint() const
|
||||
{
|
||||
return this->preferedSize_;
|
||||
}
|
||||
|
||||
void Label::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter painter(this);
|
||||
QFontMetrics metrics = getFonts()->getFontMetrics(
|
||||
this->getFontStyle(),
|
||||
this->scale() * 96.f / this->logicalDpiX() * this->devicePixelRatioF());
|
||||
painter.setFont(getFonts()->getFont(
|
||||
this->getFontStyle(), this->scale() * 96.f / this->logicalDpiX() *
|
||||
this->devicePixelRatioF()));
|
||||
|
||||
int offset = this->getOffset();
|
||||
|
||||
// draw text
|
||||
QRect textRect(offset, 0, this->width() - offset - offset, this->height());
|
||||
|
||||
int width = metrics.width(this->text_);
|
||||
Qt::Alignment alignment = !this->centered_ || width > textRect.width()
|
||||
? Qt::AlignLeft | Qt::AlignVCenter
|
||||
: Qt::AlignCenter;
|
||||
|
||||
painter.setBrush(this->palette().windowText());
|
||||
|
||||
QTextOption option(alignment);
|
||||
option.setWrapMode(QTextOption::NoWrap);
|
||||
painter.drawText(textRect, this->text_, option);
|
||||
|
||||
#if 0
|
||||
painter.setPen(QColor(255, 0, 0));
|
||||
painter.drawRect(0, 0, this->width() - 1, this->height() - 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Label::updateSize()
|
||||
{
|
||||
QFontMetrics metrics =
|
||||
getFonts()->getFontMetrics(this->fontStyle_, this->scale());
|
||||
|
||||
int width = metrics.width(this->text_) + (2 * this->getOffset());
|
||||
int height = metrics.height();
|
||||
this->preferedSize_ = QSize(width, height);
|
||||
|
||||
this->updateGeometry();
|
||||
}
|
||||
|
||||
int Label::getOffset()
|
||||
{
|
||||
return this->hasOffset_ ? int(8 * this->scale()) : 0;
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
|
|
@ -1,50 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include "singletons/Fonts.hpp"
|
||||
#include "widgets/BaseWidget.hpp"
|
||||
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
class Label : public BaseWidget
|
||||
{
|
||||
public:
|
||||
explicit Label(QString text = QString(),
|
||||
FontStyle style = FontStyle::UiMedium);
|
||||
explicit Label(BaseWidget *parent, QString text = QString(),
|
||||
FontStyle style = FontStyle::UiMedium);
|
||||
|
||||
const QString &getText() const;
|
||||
void setText(const QString &text);
|
||||
|
||||
FontStyle getFontStyle() const;
|
||||
void setFontStyle(FontStyle style);
|
||||
|
||||
bool getCentered() const;
|
||||
void setCentered(bool centered);
|
||||
|
||||
bool getHasOffset() const;
|
||||
void setHasOffset(bool hasOffset);
|
||||
|
||||
protected:
|
||||
virtual void scaleChangedEvent(float scale_) override;
|
||||
virtual void paintEvent(QPaintEvent *) override;
|
||||
|
||||
virtual QSize sizeHint() const override;
|
||||
virtual QSize minimumSizeHint() const override;
|
||||
|
||||
private:
|
||||
void updateSize();
|
||||
int getOffset();
|
||||
|
||||
QString text_;
|
||||
FontStyle fontStyle_;
|
||||
QSize preferedSize_;
|
||||
bool centered_ = false;
|
||||
bool hasOffset_ = true;
|
||||
|
||||
pajlada::Signals::SignalHolder connections_;
|
||||
};
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
#pragma once
|
||||
|
||||
#include "singletons/Fonts.hpp"
|
||||
#include "widgets/BaseWidget.hpp"
|
||||
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
class Label : public BaseWidget
|
||||
{
|
||||
public:
|
||||
explicit Label(QString text = QString(),
|
||||
FontStyle style = FontStyle::UiMedium);
|
||||
explicit Label(BaseWidget *parent, QString text = QString(),
|
||||
FontStyle style = FontStyle::UiMedium);
|
||||
|
||||
const QString &getText() const;
|
||||
void setText(const QString &text);
|
||||
|
||||
FontStyle getFontStyle() const;
|
||||
void setFontStyle(FontStyle style);
|
||||
|
||||
bool getCentered() const;
|
||||
void setCentered(bool centered);
|
||||
|
||||
bool getHasOffset() const;
|
||||
void setHasOffset(bool hasOffset);
|
||||
|
||||
protected:
|
||||
virtual void scaleChangedEvent(float scale_) override;
|
||||
virtual void paintEvent(QPaintEvent *) override;
|
||||
|
||||
virtual QSize sizeHint() const override;
|
||||
virtual QSize minimumSizeHint() const override;
|
||||
|
||||
private:
|
||||
void updateSize();
|
||||
int getOffset();
|
||||
|
||||
QString text_;
|
||||
FontStyle fontStyle_;
|
||||
QSize preferedSize_;
|
||||
bool centered_ = false;
|
||||
bool hasOffset_ = true;
|
||||
|
||||
pajlada::Signals::SignalHolder connections_;
|
||||
};
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
|
|
@ -1,134 +1,134 @@
|
|||
#include "TitlebarButton.hpp"
|
||||
|
||||
#include "BaseTheme.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
TitleBarButton::TitleBarButton()
|
||||
: Button(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
TitleBarButtonStyle TitleBarButton::getButtonStyle() const
|
||||
{
|
||||
return this->style_;
|
||||
}
|
||||
|
||||
void TitleBarButton::setButtonStyle(TitleBarButtonStyle _style)
|
||||
{
|
||||
this->style_ = _style;
|
||||
this->update();
|
||||
}
|
||||
|
||||
void TitleBarButton::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
painter.setOpacity(this->getCurrentDimAmount());
|
||||
|
||||
QColor color = this->theme->window.text;
|
||||
QColor background = this->theme->window.background;
|
||||
|
||||
int xD = this->height() / 3;
|
||||
int centerX = this->width() / 2;
|
||||
|
||||
painter.setRenderHint(QPainter::Antialiasing, false);
|
||||
|
||||
switch (this->style_)
|
||||
{
|
||||
case TitleBarButtonStyle::Minimize:
|
||||
{
|
||||
painter.fillRect(centerX - xD / 2, xD * 3 / 2, xD, 1, color);
|
||||
break;
|
||||
}
|
||||
case TitleBarButtonStyle::Maximize:
|
||||
{
|
||||
painter.setPen(color);
|
||||
painter.drawRect(centerX - xD / 2, xD, xD - 1, xD - 1);
|
||||
break;
|
||||
}
|
||||
case TitleBarButtonStyle::Unmaximize:
|
||||
{
|
||||
int xD2 = xD * 1 / 5;
|
||||
int xD3 = xD * 4 / 5;
|
||||
|
||||
painter.drawRect(centerX - xD / 2 + xD2, xD, xD3, xD3);
|
||||
painter.fillRect(centerX - xD / 2, xD + xD2, xD3, xD3,
|
||||
this->theme->window.background);
|
||||
painter.drawRect(centerX - xD / 2, xD + xD2, xD3, xD3);
|
||||
break;
|
||||
}
|
||||
case TitleBarButtonStyle::Close:
|
||||
{
|
||||
QRect rect(centerX - xD / 2, xD, xD - 1, xD - 1);
|
||||
painter.setPen(QPen(color, 1));
|
||||
|
||||
painter.drawLine(rect.topLeft(), rect.bottomRight());
|
||||
painter.drawLine(rect.topRight(), rect.bottomLeft());
|
||||
break;
|
||||
}
|
||||
case TitleBarButtonStyle::User:
|
||||
{
|
||||
color = "#999";
|
||||
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
|
||||
auto a = xD / 3;
|
||||
QPainterPath path;
|
||||
|
||||
painter.save();
|
||||
painter.translate(3, 3);
|
||||
|
||||
path.arcMoveTo(a, 4 * a, 6 * a, 6 * a, 0);
|
||||
path.arcTo(a, 4 * a, 6 * a, 6 * a, 0, 180);
|
||||
|
||||
painter.fillPath(path, color);
|
||||
|
||||
painter.setBrush(background);
|
||||
painter.drawEllipse(2 * a, 1 * a, 4 * a, 4 * a);
|
||||
|
||||
painter.setBrush(color);
|
||||
painter.drawEllipse(2.5 * a, 1.5 * a, 3 * a + 1, 3 * a);
|
||||
painter.restore();
|
||||
|
||||
break;
|
||||
}
|
||||
case TitleBarButtonStyle::Settings:
|
||||
{
|
||||
color = "#999";
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
|
||||
painter.save();
|
||||
painter.translate(3, 3);
|
||||
|
||||
auto a = xD / 3;
|
||||
QPainterPath path;
|
||||
|
||||
path.arcMoveTo(a, a, 6 * a, 6 * a, 0 - (360 / 32.0));
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
path.arcTo(a, a, 6 * a, 6 * a, i * (360 / 8.0) - (360 / 32.0),
|
||||
(360 / 32.0));
|
||||
path.arcTo(2 * a, 2 * a, 4 * a, 4 * a,
|
||||
i * (360 / 8.0) + (360 / 32.0), (360 / 32.0));
|
||||
}
|
||||
|
||||
painter.strokePath(path, color);
|
||||
painter.fillPath(path, color);
|
||||
|
||||
painter.setBrush(background);
|
||||
painter.drawEllipse(3 * a, 3 * a, 2 * a, 2 * a);
|
||||
painter.restore();
|
||||
break;
|
||||
}
|
||||
default:;
|
||||
}
|
||||
|
||||
Button::paintEvent(event);
|
||||
// this->fancyPaint(painter);
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
#include "TitlebarButton.hpp"
|
||||
|
||||
#include "BaseTheme.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
TitleBarButton::TitleBarButton()
|
||||
: Button(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
TitleBarButtonStyle TitleBarButton::getButtonStyle() const
|
||||
{
|
||||
return this->style_;
|
||||
}
|
||||
|
||||
void TitleBarButton::setButtonStyle(TitleBarButtonStyle _style)
|
||||
{
|
||||
this->style_ = _style;
|
||||
this->update();
|
||||
}
|
||||
|
||||
void TitleBarButton::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
painter.setOpacity(this->getCurrentDimAmount());
|
||||
|
||||
QColor color = this->theme->window.text;
|
||||
QColor background = this->theme->window.background;
|
||||
|
||||
int xD = this->height() / 3;
|
||||
int centerX = this->width() / 2;
|
||||
|
||||
painter.setRenderHint(QPainter::Antialiasing, false);
|
||||
|
||||
switch (this->style_)
|
||||
{
|
||||
case TitleBarButtonStyle::Minimize:
|
||||
{
|
||||
painter.fillRect(centerX - xD / 2, xD * 3 / 2, xD, 1, color);
|
||||
break;
|
||||
}
|
||||
case TitleBarButtonStyle::Maximize:
|
||||
{
|
||||
painter.setPen(color);
|
||||
painter.drawRect(centerX - xD / 2, xD, xD - 1, xD - 1);
|
||||
break;
|
||||
}
|
||||
case TitleBarButtonStyle::Unmaximize:
|
||||
{
|
||||
int xD2 = xD * 1 / 5;
|
||||
int xD3 = xD * 4 / 5;
|
||||
|
||||
painter.drawRect(centerX - xD / 2 + xD2, xD, xD3, xD3);
|
||||
painter.fillRect(centerX - xD / 2, xD + xD2, xD3, xD3,
|
||||
this->theme->window.background);
|
||||
painter.drawRect(centerX - xD / 2, xD + xD2, xD3, xD3);
|
||||
break;
|
||||
}
|
||||
case TitleBarButtonStyle::Close:
|
||||
{
|
||||
QRect rect(centerX - xD / 2, xD, xD - 1, xD - 1);
|
||||
painter.setPen(QPen(color, 1));
|
||||
|
||||
painter.drawLine(rect.topLeft(), rect.bottomRight());
|
||||
painter.drawLine(rect.topRight(), rect.bottomLeft());
|
||||
break;
|
||||
}
|
||||
case TitleBarButtonStyle::User:
|
||||
{
|
||||
color = "#999";
|
||||
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
|
||||
auto a = xD / 3;
|
||||
QPainterPath path;
|
||||
|
||||
painter.save();
|
||||
painter.translate(3, 3);
|
||||
|
||||
path.arcMoveTo(a, 4 * a, 6 * a, 6 * a, 0);
|
||||
path.arcTo(a, 4 * a, 6 * a, 6 * a, 0, 180);
|
||||
|
||||
painter.fillPath(path, color);
|
||||
|
||||
painter.setBrush(background);
|
||||
painter.drawEllipse(2 * a, 1 * a, 4 * a, 4 * a);
|
||||
|
||||
painter.setBrush(color);
|
||||
painter.drawEllipse(2.5 * a, 1.5 * a, 3 * a + 1, 3 * a);
|
||||
painter.restore();
|
||||
|
||||
break;
|
||||
}
|
||||
case TitleBarButtonStyle::Settings:
|
||||
{
|
||||
color = "#999";
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
|
||||
painter.save();
|
||||
painter.translate(3, 3);
|
||||
|
||||
auto a = xD / 3;
|
||||
QPainterPath path;
|
||||
|
||||
path.arcMoveTo(a, a, 6 * a, 6 * a, 0 - (360 / 32.0));
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
path.arcTo(a, a, 6 * a, 6 * a, i * (360 / 8.0) - (360 / 32.0),
|
||||
(360 / 32.0));
|
||||
path.arcTo(2 * a, 2 * a, 4 * a, 4 * a,
|
||||
i * (360 / 8.0) + (360 / 32.0), (360 / 32.0));
|
||||
}
|
||||
|
||||
painter.strokePath(path, color);
|
||||
painter.fillPath(path, color);
|
||||
|
||||
painter.setBrush(background);
|
||||
painter.drawEllipse(3 * a, 3 * a, 2 * a, 2 * a);
|
||||
painter.restore();
|
||||
break;
|
||||
}
|
||||
default:;
|
||||
}
|
||||
|
||||
Button::paintEvent(event);
|
||||
// this->fancyPaint(painter);
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include "widgets/helper/Button.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
enum class TitleBarButtonStyle {
|
||||
None = 0,
|
||||
Minimize = 1,
|
||||
Maximize = 2,
|
||||
Unmaximize = 4,
|
||||
Close = 8,
|
||||
User = 16,
|
||||
Settings = 32
|
||||
};
|
||||
|
||||
class TitleBarButton : public Button
|
||||
{
|
||||
public:
|
||||
TitleBarButton();
|
||||
|
||||
TitleBarButtonStyle getButtonStyle() const;
|
||||
void setButtonStyle(TitleBarButtonStyle style_);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
|
||||
private:
|
||||
TitleBarButtonStyle style_;
|
||||
};
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
#pragma once
|
||||
|
||||
#include "widgets/helper/Button.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
|
||||
enum class TitleBarButtonStyle {
|
||||
None = 0,
|
||||
Minimize = 1,
|
||||
Maximize = 2,
|
||||
Unmaximize = 4,
|
||||
Close = 8,
|
||||
User = 16,
|
||||
Settings = 32
|
||||
};
|
||||
|
||||
class TitleBarButton : public Button
|
||||
{
|
||||
public:
|
||||
TitleBarButton();
|
||||
|
||||
TitleBarButtonStyle getButtonStyle() const;
|
||||
void setButtonStyle(TitleBarButtonStyle style_);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
|
||||
private:
|
||||
TitleBarButtonStyle style_;
|
||||
};
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
class Channel;
|
||||
class ChannelView;
|
||||
using ChannelPtr = std::shared_ptr<Channel>;
|
||||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
class Channel;
|
||||
class ChannelView;
|
||||
using ChannelPtr = std::shared_ptr<Channel>;
|
||||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,170 +1,170 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
# include <fmt/format.h>
|
||||
# include <irccommand.h>
|
||||
# include <ircconnection.h>
|
||||
# include <rapidjson/document.h>
|
||||
# include <rapidjson/error/en.h>
|
||||
# include <rapidjson/error/error.h>
|
||||
# include <IrcMessage>
|
||||
# include <QAbstractListModel>
|
||||
# include <QAbstractNativeEventFilter>
|
||||
# include <QAction>
|
||||
# include <QApplication>
|
||||
# include <QBrush>
|
||||
# include <QBuffer>
|
||||
# include <QButtonGroup>
|
||||
# include <QByteArray>
|
||||
# include <QCheckBox>
|
||||
# include <QClipboard>
|
||||
# include <QColor>
|
||||
# include <QComboBox>
|
||||
# include <QCompleter>
|
||||
# include <QCoreApplication>
|
||||
# include <QDateTime>
|
||||
# include <QDebug>
|
||||
# include <QDesktopServices>
|
||||
# include <QDialog>
|
||||
# include <QDialogButtonBox>
|
||||
# include <QDir>
|
||||
# include <QDockWidget>
|
||||
# include <QDrag>
|
||||
# include <QDragEnterEvent>
|
||||
# include <QElapsedTimer>
|
||||
# include <QEventLoop>
|
||||
# include <QFile>
|
||||
# include <QFileDialog>
|
||||
# include <QFileInfo>
|
||||
# include <QFlags>
|
||||
# include <QFont>
|
||||
# include <QFontDatabase>
|
||||
# include <QFontDialog>
|
||||
# include <QFontMetrics>
|
||||
# include <QFormLayout>
|
||||
# include <QGraphicsBlurEffect>
|
||||
# include <QGroupBox>
|
||||
# include <QHBoxLayout>
|
||||
# include <QHeaderView>
|
||||
# include <QIcon>
|
||||
# include <QImageReader>
|
||||
# include <QJsonArray>
|
||||
# include <QJsonDocument>
|
||||
# include <QJsonObject>
|
||||
# include <QJsonValue>
|
||||
# include <QKeyEvent>
|
||||
# include <QLabel>
|
||||
# include <QLayout>
|
||||
# include <QLibrary>
|
||||
# include <QLineEdit>
|
||||
# include <QList>
|
||||
# include <QListView>
|
||||
# include <QListWidget>
|
||||
# include <QMap>
|
||||
# include <QMediaPlayer>
|
||||
# include <QMenu>
|
||||
# include <QMessageBox>
|
||||
# include <QMimeData>
|
||||
# include <QMouseEvent>
|
||||
# include <QMutex>
|
||||
# include <QMutexLocker>
|
||||
# include <QNetworkAccessManager>
|
||||
# include <QNetworkReply>
|
||||
# include <QNetworkRequest>
|
||||
# include <QObject>
|
||||
# include <QPaintEvent>
|
||||
# include <QPainter>
|
||||
# include <QPainterPath>
|
||||
# include <QPalette>
|
||||
# include <QPixmap>
|
||||
# include <QPoint>
|
||||
# include <QProcess>
|
||||
# include <QPropertyAnimation>
|
||||
# include <QPushButton>
|
||||
# include <QRadialGradient>
|
||||
# include <QRect>
|
||||
# include <QRegularExpression>
|
||||
# include <QRunnable>
|
||||
# include <QScroller>
|
||||
# include <QShortcut>
|
||||
# include <QSizePolicy>
|
||||
# include <QSlider>
|
||||
# include <QStackedLayout>
|
||||
# include <QStandardPaths>
|
||||
# include <QString>
|
||||
# include <QStyle>
|
||||
# include <QStyleOption>
|
||||
# include <QTabWidget>
|
||||
# include <QTextEdit>
|
||||
# include <QThread>
|
||||
# include <QThreadPool>
|
||||
# include <QTime>
|
||||
# include <QTimer>
|
||||
# include <QUrl>
|
||||
# include <QUuid>
|
||||
# include <QVBoxLayout>
|
||||
# include <QVariant>
|
||||
# include <QVector>
|
||||
# include <QWheelEvent>
|
||||
# include <QWidget>
|
||||
# include <QtCore/QVariant>
|
||||
# include <QtGlobal>
|
||||
# include <QtWidgets/QAction>
|
||||
# include <QtWidgets/QApplication>
|
||||
# include <QtWidgets/QButtonGroup>
|
||||
# include <QtWidgets/QDialog>
|
||||
# include <QtWidgets/QDialogButtonBox>
|
||||
# include <QtWidgets/QFormLayout>
|
||||
# include <QtWidgets/QHBoxLayout>
|
||||
# include <QtWidgets/QHeaderView>
|
||||
# include <QtWidgets/QLabel>
|
||||
# include <QtWidgets/QLineEdit>
|
||||
# include <QtWidgets/QPushButton>
|
||||
# include <QtWidgets/QTabWidget>
|
||||
# include <QtWidgets/QVBoxLayout>
|
||||
# include <algorithm>
|
||||
# include <boost/current_function.hpp>
|
||||
# include <boost/foreach.hpp>
|
||||
# include <boost/noncopyable.hpp>
|
||||
# include <boost/optional.hpp>
|
||||
# include <cassert>
|
||||
# include <chrono>
|
||||
# include <cinttypes>
|
||||
# include <climits>
|
||||
# include <cmath>
|
||||
# include <cstdint>
|
||||
# include <ctime>
|
||||
# include <functional>
|
||||
# include <future>
|
||||
# include <list>
|
||||
# include <map>
|
||||
# include <memory>
|
||||
# include <mutex>
|
||||
# include <pajlada/serialize.hpp>
|
||||
# include <pajlada/settings/setting.hpp>
|
||||
# include <pajlada/settings/settinglistener.hpp>
|
||||
# include <pajlada/signals/connection.hpp>
|
||||
# include <pajlada/signals/signal.hpp>
|
||||
# include <random>
|
||||
# include <set>
|
||||
# include <string>
|
||||
# include <thread>
|
||||
# include <tuple>
|
||||
# include <type_traits>
|
||||
# include <unordered_map>
|
||||
# include <unordered_set>
|
||||
# include <vector>
|
||||
|
||||
# ifndef UNUSED
|
||||
# define UNUSED(x) (void)(x)
|
||||
# endif
|
||||
|
||||
# ifndef ATTR_UNUSED
|
||||
# ifdef Q_OS_WIN
|
||||
# define ATTR_UNUSED
|
||||
# else
|
||||
# define ATTR_UNUSED __attribute__((unused))
|
||||
# endif
|
||||
# endif
|
||||
|
||||
#endif
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
# include <fmt/format.h>
|
||||
# include <irccommand.h>
|
||||
# include <ircconnection.h>
|
||||
# include <rapidjson/document.h>
|
||||
# include <rapidjson/error/en.h>
|
||||
# include <rapidjson/error/error.h>
|
||||
# include <IrcMessage>
|
||||
# include <QAbstractListModel>
|
||||
# include <QAbstractNativeEventFilter>
|
||||
# include <QAction>
|
||||
# include <QApplication>
|
||||
# include <QBrush>
|
||||
# include <QBuffer>
|
||||
# include <QButtonGroup>
|
||||
# include <QByteArray>
|
||||
# include <QCheckBox>
|
||||
# include <QClipboard>
|
||||
# include <QColor>
|
||||
# include <QComboBox>
|
||||
# include <QCompleter>
|
||||
# include <QCoreApplication>
|
||||
# include <QDateTime>
|
||||
# include <QDebug>
|
||||
# include <QDesktopServices>
|
||||
# include <QDialog>
|
||||
# include <QDialogButtonBox>
|
||||
# include <QDir>
|
||||
# include <QDockWidget>
|
||||
# include <QDrag>
|
||||
# include <QDragEnterEvent>
|
||||
# include <QElapsedTimer>
|
||||
# include <QEventLoop>
|
||||
# include <QFile>
|
||||
# include <QFileDialog>
|
||||
# include <QFileInfo>
|
||||
# include <QFlags>
|
||||
# include <QFont>
|
||||
# include <QFontDatabase>
|
||||
# include <QFontDialog>
|
||||
# include <QFontMetrics>
|
||||
# include <QFormLayout>
|
||||
# include <QGraphicsBlurEffect>
|
||||
# include <QGroupBox>
|
||||
# include <QHBoxLayout>
|
||||
# include <QHeaderView>
|
||||
# include <QIcon>
|
||||
# include <QImageReader>
|
||||
# include <QJsonArray>
|
||||
# include <QJsonDocument>
|
||||
# include <QJsonObject>
|
||||
# include <QJsonValue>
|
||||
# include <QKeyEvent>
|
||||
# include <QLabel>
|
||||
# include <QLayout>
|
||||
# include <QLibrary>
|
||||
# include <QLineEdit>
|
||||
# include <QList>
|
||||
# include <QListView>
|
||||
# include <QListWidget>
|
||||
# include <QMap>
|
||||
# include <QMediaPlayer>
|
||||
# include <QMenu>
|
||||
# include <QMessageBox>
|
||||
# include <QMimeData>
|
||||
# include <QMouseEvent>
|
||||
# include <QMutex>
|
||||
# include <QMutexLocker>
|
||||
# include <QNetworkAccessManager>
|
||||
# include <QNetworkReply>
|
||||
# include <QNetworkRequest>
|
||||
# include <QObject>
|
||||
# include <QPaintEvent>
|
||||
# include <QPainter>
|
||||
# include <QPainterPath>
|
||||
# include <QPalette>
|
||||
# include <QPixmap>
|
||||
# include <QPoint>
|
||||
# include <QProcess>
|
||||
# include <QPropertyAnimation>
|
||||
# include <QPushButton>
|
||||
# include <QRadialGradient>
|
||||
# include <QRect>
|
||||
# include <QRegularExpression>
|
||||
# include <QRunnable>
|
||||
# include <QScroller>
|
||||
# include <QShortcut>
|
||||
# include <QSizePolicy>
|
||||
# include <QSlider>
|
||||
# include <QStackedLayout>
|
||||
# include <QStandardPaths>
|
||||
# include <QString>
|
||||
# include <QStyle>
|
||||
# include <QStyleOption>
|
||||
# include <QTabWidget>
|
||||
# include <QTextEdit>
|
||||
# include <QThread>
|
||||
# include <QThreadPool>
|
||||
# include <QTime>
|
||||
# include <QTimer>
|
||||
# include <QUrl>
|
||||
# include <QUuid>
|
||||
# include <QVBoxLayout>
|
||||
# include <QVariant>
|
||||
# include <QVector>
|
||||
# include <QWheelEvent>
|
||||
# include <QWidget>
|
||||
# include <QtCore/QVariant>
|
||||
# include <QtGlobal>
|
||||
# include <QtWidgets/QAction>
|
||||
# include <QtWidgets/QApplication>
|
||||
# include <QtWidgets/QButtonGroup>
|
||||
# include <QtWidgets/QDialog>
|
||||
# include <QtWidgets/QDialogButtonBox>
|
||||
# include <QtWidgets/QFormLayout>
|
||||
# include <QtWidgets/QHBoxLayout>
|
||||
# include <QtWidgets/QHeaderView>
|
||||
# include <QtWidgets/QLabel>
|
||||
# include <QtWidgets/QLineEdit>
|
||||
# include <QtWidgets/QPushButton>
|
||||
# include <QtWidgets/QTabWidget>
|
||||
# include <QtWidgets/QVBoxLayout>
|
||||
# include <algorithm>
|
||||
# include <boost/current_function.hpp>
|
||||
# include <boost/foreach.hpp>
|
||||
# include <boost/noncopyable.hpp>
|
||||
# include <boost/optional.hpp>
|
||||
# include <cassert>
|
||||
# include <chrono>
|
||||
# include <cinttypes>
|
||||
# include <climits>
|
||||
# include <cmath>
|
||||
# include <cstdint>
|
||||
# include <ctime>
|
||||
# include <functional>
|
||||
# include <future>
|
||||
# include <list>
|
||||
# include <map>
|
||||
# include <memory>
|
||||
# include <mutex>
|
||||
# include <pajlada/serialize.hpp>
|
||||
# include <pajlada/settings/setting.hpp>
|
||||
# include <pajlada/settings/settinglistener.hpp>
|
||||
# include <pajlada/signals/connection.hpp>
|
||||
# include <pajlada/signals/signal.hpp>
|
||||
# include <random>
|
||||
# include <set>
|
||||
# include <string>
|
||||
# include <thread>
|
||||
# include <tuple>
|
||||
# include <type_traits>
|
||||
# include <unordered_map>
|
||||
# include <unordered_set>
|
||||
# include <vector>
|
||||
|
||||
# ifndef UNUSED
|
||||
# define UNUSED(x) (void)(x)
|
||||
# endif
|
||||
|
||||
# ifndef ATTR_UNUSED
|
||||
# ifdef Q_OS_WIN
|
||||
# define ATTR_UNUSED
|
||||
# else
|
||||
# define ATTR_UNUSED __attribute__((unused))
|
||||
# endif
|
||||
# endif
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,47 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <mutex>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename T>
|
||||
class Atomic : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
Atomic()
|
||||
{
|
||||
}
|
||||
|
||||
Atomic(T &&val)
|
||||
: value_(val)
|
||||
{
|
||||
}
|
||||
|
||||
T get() const
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->mutex_);
|
||||
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
void set(const T &val)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->mutex_);
|
||||
|
||||
this->value_ = val;
|
||||
}
|
||||
|
||||
void set(T &&val)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->mutex_);
|
||||
|
||||
this->value_ = std::move(val);
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex_;
|
||||
T value_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <mutex>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename T>
|
||||
class Atomic : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
Atomic()
|
||||
{
|
||||
}
|
||||
|
||||
Atomic(T &&val)
|
||||
: value_(val)
|
||||
{
|
||||
}
|
||||
|
||||
T get() const
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->mutex_);
|
||||
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
void set(const T &val)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->mutex_);
|
||||
|
||||
this->value_ = val;
|
||||
}
|
||||
|
||||
void set(T &&val)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->mutex_);
|
||||
|
||||
this->value_ = std::move(val);
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex_;
|
||||
T value_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,49 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/Aliases.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "common/ProviderId.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/preprocessor.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum class HighlightState {
|
||||
None,
|
||||
Highlighted,
|
||||
NewMessage,
|
||||
};
|
||||
|
||||
inline QString qS(const std::string &string)
|
||||
{
|
||||
return QString::fromStdString(string);
|
||||
}
|
||||
|
||||
const Qt::KeyboardModifiers showSplitOverlayModifiers =
|
||||
Qt::ControlModifier | Qt::AltModifier;
|
||||
const Qt::KeyboardModifiers showAddSplitRegions =
|
||||
Qt::ControlModifier | Qt::AltModifier;
|
||||
const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
|
||||
|
||||
static const char *ANONYMOUS_USERNAME_LABEL ATTR_UNUSED = " - anonymous - ";
|
||||
|
||||
template <typename T>
|
||||
std::weak_ptr<T> weakOf(T *element)
|
||||
{
|
||||
return element->shared_from_this();
|
||||
}
|
||||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
|
||||
enum class CopyMode {
|
||||
Everything,
|
||||
OnlyTextAndEmotes,
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "common/Aliases.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "common/ProviderId.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/preprocessor.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum class HighlightState {
|
||||
None,
|
||||
Highlighted,
|
||||
NewMessage,
|
||||
};
|
||||
|
||||
inline QString qS(const std::string &string)
|
||||
{
|
||||
return QString::fromStdString(string);
|
||||
}
|
||||
|
||||
const Qt::KeyboardModifiers showSplitOverlayModifiers =
|
||||
Qt::ControlModifier | Qt::AltModifier;
|
||||
const Qt::KeyboardModifiers showAddSplitRegions =
|
||||
Qt::ControlModifier | Qt::AltModifier;
|
||||
const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
|
||||
|
||||
static const char *ANONYMOUS_USERNAME_LABEL ATTR_UNUSED = " - anonymous - ";
|
||||
|
||||
template <typename T>
|
||||
std::weak_ptr<T> weakOf(T *element)
|
||||
{
|
||||
return element->shared_from_this();
|
||||
}
|
||||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
|
||||
enum class CopyMode {
|
||||
Everything,
|
||||
OnlyTextAndEmotes,
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,195 +1,195 @@
|
|||
#include "common/CompletionModel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Common.hpp"
|
||||
#include "common/UsernameSet.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandController.hpp"
|
||||
#include "debug/Benchmark.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
#include <QtAlgorithms>
|
||||
#include <utility>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
//
|
||||
// TaggedString
|
||||
//
|
||||
|
||||
CompletionModel::TaggedString::TaggedString(const QString &_string, Type _type)
|
||||
: string(_string)
|
||||
, type(_type)
|
||||
{
|
||||
}
|
||||
|
||||
bool CompletionModel::TaggedString::isEmote() const
|
||||
{
|
||||
return this->type > Type::EmoteStart && this->type < Type::EmoteEnd;
|
||||
}
|
||||
|
||||
bool CompletionModel::TaggedString::operator<(const TaggedString &that) const
|
||||
{
|
||||
if (this->isEmote() != that.isEmote())
|
||||
{
|
||||
return this->isEmote();
|
||||
}
|
||||
|
||||
// try comparing insensitively, if they are the same then senstively
|
||||
// (fixes order of LuL and LUL)
|
||||
int k = QString::compare(this->string, that.string, Qt::CaseInsensitive);
|
||||
if (k == 0)
|
||||
return this->string > that.string;
|
||||
|
||||
return k < 0;
|
||||
}
|
||||
|
||||
//
|
||||
// CompletionModel
|
||||
//
|
||||
CompletionModel::CompletionModel(Channel &channel)
|
||||
: channel_(channel)
|
||||
{
|
||||
}
|
||||
|
||||
int CompletionModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
QVariant CompletionModel::data(const QModelIndex &index, int) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->itemsMutex_);
|
||||
|
||||
auto it = this->items_.begin();
|
||||
std::advance(it, index.row());
|
||||
return QVariant(it->string);
|
||||
}
|
||||
|
||||
int CompletionModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->itemsMutex_);
|
||||
|
||||
return this->items_.size();
|
||||
}
|
||||
|
||||
void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||
{
|
||||
std::function<void(const QString &, TaggedString::Type)> addString;
|
||||
if (getSettings()->prefixOnlyEmoteCompletion)
|
||||
{
|
||||
addString = [&](const QString &str, TaggedString::Type type) {
|
||||
if (str.startsWith(prefix, Qt::CaseInsensitive))
|
||||
this->items_.emplace(str + " ", type);
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
addString = [&](const QString &str, TaggedString::Type type) {
|
||||
if (str.contains(prefix, Qt::CaseInsensitive))
|
||||
this->items_.emplace(str + " ", type);
|
||||
};
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> guard(this->itemsMutex_);
|
||||
this->items_.clear();
|
||||
|
||||
if (prefix.length() < 2)
|
||||
return;
|
||||
|
||||
if (auto channel = dynamic_cast<TwitchChannel *>(&this->channel_))
|
||||
{
|
||||
// account emotes
|
||||
if (auto account = getApp()->accounts->twitch.getCurrent())
|
||||
{
|
||||
for (const auto &emote : account->accessEmotes()->allEmoteNames)
|
||||
{
|
||||
// XXX: No way to discern between a twitch global emote and sub
|
||||
// emote right now
|
||||
addString(emote.string, TaggedString::Type::TwitchGlobalEmote);
|
||||
}
|
||||
}
|
||||
|
||||
// Usernames
|
||||
if (prefix.length() >= UsernameSet::PrefixLength)
|
||||
{
|
||||
auto usernames = channel->accessChatters();
|
||||
|
||||
QString usernamePrefix = prefix;
|
||||
QString usernamePostfix =
|
||||
isFirstWord && getSettings()->mentionUsersWithComma ? ","
|
||||
: QString();
|
||||
|
||||
if (usernamePrefix.startsWith("@"))
|
||||
{
|
||||
usernamePrefix.remove(0, 1);
|
||||
for (const auto &name :
|
||||
usernames->subrange(Prefix(usernamePrefix)))
|
||||
{
|
||||
addString("@" + name + usernamePostfix,
|
||||
TaggedString::Type::Username);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto &name :
|
||||
usernames->subrange(Prefix(usernamePrefix)))
|
||||
{
|
||||
addString(name + usernamePostfix,
|
||||
TaggedString::Type::Username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bttv Global
|
||||
for (auto &emote : *channel->globalBttv().emotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::BTTVChannelEmote);
|
||||
}
|
||||
|
||||
// Ffz Global
|
||||
for (auto &emote : *channel->globalFfz().emotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::FFZChannelEmote);
|
||||
}
|
||||
|
||||
// Bttv Channel
|
||||
for (auto &emote : *channel->bttvEmotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
|
||||
}
|
||||
|
||||
// Ffz Channel
|
||||
for (auto &emote : *channel->ffzEmotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
|
||||
}
|
||||
|
||||
// Emojis
|
||||
if (prefix.startsWith(":"))
|
||||
{
|
||||
const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes;
|
||||
for (auto &m : emojiShortCodes)
|
||||
{
|
||||
addString(":" + m + ":", TaggedString::Type::Emoji);
|
||||
}
|
||||
}
|
||||
|
||||
// Commands
|
||||
for (auto &command : getApp()->commands->items_)
|
||||
{
|
||||
addString(command.name, TaggedString::Command);
|
||||
}
|
||||
|
||||
for (auto &command : getApp()->commands->getDefaultTwitchCommandList())
|
||||
{
|
||||
addString(command, TaggedString::Command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "common/CompletionModel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Common.hpp"
|
||||
#include "common/UsernameSet.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandController.hpp"
|
||||
#include "debug/Benchmark.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
#include <QtAlgorithms>
|
||||
#include <utility>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
//
|
||||
// TaggedString
|
||||
//
|
||||
|
||||
CompletionModel::TaggedString::TaggedString(const QString &_string, Type _type)
|
||||
: string(_string)
|
||||
, type(_type)
|
||||
{
|
||||
}
|
||||
|
||||
bool CompletionModel::TaggedString::isEmote() const
|
||||
{
|
||||
return this->type > Type::EmoteStart && this->type < Type::EmoteEnd;
|
||||
}
|
||||
|
||||
bool CompletionModel::TaggedString::operator<(const TaggedString &that) const
|
||||
{
|
||||
if (this->isEmote() != that.isEmote())
|
||||
{
|
||||
return this->isEmote();
|
||||
}
|
||||
|
||||
// try comparing insensitively, if they are the same then senstively
|
||||
// (fixes order of LuL and LUL)
|
||||
int k = QString::compare(this->string, that.string, Qt::CaseInsensitive);
|
||||
if (k == 0)
|
||||
return this->string > that.string;
|
||||
|
||||
return k < 0;
|
||||
}
|
||||
|
||||
//
|
||||
// CompletionModel
|
||||
//
|
||||
CompletionModel::CompletionModel(Channel &channel)
|
||||
: channel_(channel)
|
||||
{
|
||||
}
|
||||
|
||||
int CompletionModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
QVariant CompletionModel::data(const QModelIndex &index, int) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->itemsMutex_);
|
||||
|
||||
auto it = this->items_.begin();
|
||||
std::advance(it, index.row());
|
||||
return QVariant(it->string);
|
||||
}
|
||||
|
||||
int CompletionModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->itemsMutex_);
|
||||
|
||||
return this->items_.size();
|
||||
}
|
||||
|
||||
void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||
{
|
||||
std::function<void(const QString &, TaggedString::Type)> addString;
|
||||
if (getSettings()->prefixOnlyEmoteCompletion)
|
||||
{
|
||||
addString = [&](const QString &str, TaggedString::Type type) {
|
||||
if (str.startsWith(prefix, Qt::CaseInsensitive))
|
||||
this->items_.emplace(str + " ", type);
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
addString = [&](const QString &str, TaggedString::Type type) {
|
||||
if (str.contains(prefix, Qt::CaseInsensitive))
|
||||
this->items_.emplace(str + " ", type);
|
||||
};
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> guard(this->itemsMutex_);
|
||||
this->items_.clear();
|
||||
|
||||
if (prefix.length() < 2)
|
||||
return;
|
||||
|
||||
if (auto channel = dynamic_cast<TwitchChannel *>(&this->channel_))
|
||||
{
|
||||
// account emotes
|
||||
if (auto account = getApp()->accounts->twitch.getCurrent())
|
||||
{
|
||||
for (const auto &emote : account->accessEmotes()->allEmoteNames)
|
||||
{
|
||||
// XXX: No way to discern between a twitch global emote and sub
|
||||
// emote right now
|
||||
addString(emote.string, TaggedString::Type::TwitchGlobalEmote);
|
||||
}
|
||||
}
|
||||
|
||||
// Usernames
|
||||
if (prefix.length() >= UsernameSet::PrefixLength)
|
||||
{
|
||||
auto usernames = channel->accessChatters();
|
||||
|
||||
QString usernamePrefix = prefix;
|
||||
QString usernamePostfix =
|
||||
isFirstWord && getSettings()->mentionUsersWithComma ? ","
|
||||
: QString();
|
||||
|
||||
if (usernamePrefix.startsWith("@"))
|
||||
{
|
||||
usernamePrefix.remove(0, 1);
|
||||
for (const auto &name :
|
||||
usernames->subrange(Prefix(usernamePrefix)))
|
||||
{
|
||||
addString("@" + name + usernamePostfix,
|
||||
TaggedString::Type::Username);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto &name :
|
||||
usernames->subrange(Prefix(usernamePrefix)))
|
||||
{
|
||||
addString(name + usernamePostfix,
|
||||
TaggedString::Type::Username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bttv Global
|
||||
for (auto &emote : *channel->globalBttv().emotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::BTTVChannelEmote);
|
||||
}
|
||||
|
||||
// Ffz Global
|
||||
for (auto &emote : *channel->globalFfz().emotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::FFZChannelEmote);
|
||||
}
|
||||
|
||||
// Bttv Channel
|
||||
for (auto &emote : *channel->bttvEmotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
|
||||
}
|
||||
|
||||
// Ffz Channel
|
||||
for (auto &emote : *channel->ffzEmotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
|
||||
}
|
||||
|
||||
// Emojis
|
||||
if (prefix.startsWith(":"))
|
||||
{
|
||||
const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes;
|
||||
for (auto &m : emojiShortCodes)
|
||||
{
|
||||
addString(":" + m + ":", TaggedString::Type::Emoji);
|
||||
}
|
||||
}
|
||||
|
||||
// Commands
|
||||
for (auto &command : getApp()->commands->items_)
|
||||
{
|
||||
addString(command.name, TaggedString::Command);
|
||||
}
|
||||
|
||||
for (auto &command : getApp()->commands->getDefaultTwitchCommandList())
|
||||
{
|
||||
addString(command, TaggedString::Command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,60 +1,60 @@
|
|||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Channel;
|
||||
|
||||
class CompletionModel : public QAbstractListModel
|
||||
{
|
||||
struct TaggedString {
|
||||
enum Type {
|
||||
Username,
|
||||
|
||||
// emotes
|
||||
EmoteStart,
|
||||
FFZGlobalEmote,
|
||||
FFZChannelEmote,
|
||||
BTTVGlobalEmote,
|
||||
BTTVChannelEmote,
|
||||
TwitchGlobalEmote,
|
||||
TwitchSubscriberEmote,
|
||||
Emoji,
|
||||
EmoteEnd,
|
||||
// end emotes
|
||||
|
||||
Command,
|
||||
};
|
||||
|
||||
TaggedString(const QString &string, Type type);
|
||||
|
||||
bool isEmote() const;
|
||||
bool operator<(const TaggedString &that) const;
|
||||
|
||||
QString string;
|
||||
Type type;
|
||||
};
|
||||
|
||||
public:
|
||||
CompletionModel(Channel &channel);
|
||||
|
||||
virtual int columnCount(const QModelIndex &) const override;
|
||||
virtual QVariant data(const QModelIndex &index, int) const override;
|
||||
virtual int rowCount(const QModelIndex &) const override;
|
||||
|
||||
void refresh(const QString &prefix, bool isFirstWord = false);
|
||||
|
||||
private:
|
||||
TaggedString createUser(const QString &str);
|
||||
|
||||
std::set<TaggedString> items_;
|
||||
mutable std::mutex itemsMutex_;
|
||||
Channel &channel_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Channel;
|
||||
|
||||
class CompletionModel : public QAbstractListModel
|
||||
{
|
||||
struct TaggedString {
|
||||
enum Type {
|
||||
Username,
|
||||
|
||||
// emotes
|
||||
EmoteStart,
|
||||
FFZGlobalEmote,
|
||||
FFZChannelEmote,
|
||||
BTTVGlobalEmote,
|
||||
BTTVChannelEmote,
|
||||
TwitchGlobalEmote,
|
||||
TwitchSubscriberEmote,
|
||||
Emoji,
|
||||
EmoteEnd,
|
||||
// end emotes
|
||||
|
||||
Command,
|
||||
};
|
||||
|
||||
TaggedString(const QString &string, Type type);
|
||||
|
||||
bool isEmote() const;
|
||||
bool operator<(const TaggedString &that) const;
|
||||
|
||||
QString string;
|
||||
Type type;
|
||||
};
|
||||
|
||||
public:
|
||||
CompletionModel(Channel &channel);
|
||||
|
||||
virtual int columnCount(const QModelIndex &) const override;
|
||||
virtual QVariant data(const QModelIndex &index, int) const override;
|
||||
virtual int rowCount(const QModelIndex &) const override;
|
||||
|
||||
void refresh(const QString &prefix, bool isFirstWord = false);
|
||||
|
||||
private:
|
||||
TaggedString createUser(const QString &str);
|
||||
|
||||
std::set<TaggedString> items_;
|
||||
mutable std::mutex itemsMutex_;
|
||||
Channel &channel_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,73 +1,73 @@
|
|||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename T>
|
||||
class NullablePtr
|
||||
{
|
||||
public:
|
||||
NullablePtr()
|
||||
: element_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
NullablePtr(T *element)
|
||||
: element_(element)
|
||||
{
|
||||
}
|
||||
|
||||
T *operator->() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return element_;
|
||||
}
|
||||
|
||||
typename std::add_lvalue_reference<T>::type operator*() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return *element_;
|
||||
}
|
||||
|
||||
T *get() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return this->element_;
|
||||
}
|
||||
|
||||
bool isNull() const
|
||||
{
|
||||
return this->element_ == nullptr;
|
||||
}
|
||||
|
||||
bool hasElement() const
|
||||
{
|
||||
return this->element_ != nullptr;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return this->hasElement();
|
||||
}
|
||||
|
||||
bool operator!() const
|
||||
{
|
||||
return !this->hasElement();
|
||||
}
|
||||
|
||||
template <typename X = T,
|
||||
typename = std::enable_if_t<!std::is_const<X>::value>>
|
||||
operator NullablePtr<const T>() const
|
||||
{
|
||||
return NullablePtr<const T>(this->element_);
|
||||
}
|
||||
|
||||
private:
|
||||
T *element_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename T>
|
||||
class NullablePtr
|
||||
{
|
||||
public:
|
||||
NullablePtr()
|
||||
: element_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
NullablePtr(T *element)
|
||||
: element_(element)
|
||||
{
|
||||
}
|
||||
|
||||
T *operator->() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return element_;
|
||||
}
|
||||
|
||||
typename std::add_lvalue_reference<T>::type operator*() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return *element_;
|
||||
}
|
||||
|
||||
T *get() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return this->element_;
|
||||
}
|
||||
|
||||
bool isNull() const
|
||||
{
|
||||
return this->element_ == nullptr;
|
||||
}
|
||||
|
||||
bool hasElement() const
|
||||
{
|
||||
return this->element_ != nullptr;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return this->hasElement();
|
||||
}
|
||||
|
||||
bool operator!() const
|
||||
{
|
||||
return !this->hasElement();
|
||||
}
|
||||
|
||||
template <typename X = T,
|
||||
typename = std::enable_if_t<!std::is_const<X>::value>>
|
||||
operator NullablePtr<const T>() const
|
||||
{
|
||||
return NullablePtr<const T>(this->element_);
|
||||
}
|
||||
|
||||
private:
|
||||
T *element_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum class ProviderId { Twitch, Irc };
|
||||
//
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum class ProviderId { Twitch, Irc };
|
||||
//
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,247 +1,247 @@
|
|||
#pragma once
|
||||
|
||||
#include <QStandardItemModel>
|
||||
#include <QTimer>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
#include <shared_mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "debug/AssertInGuiThread.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename TVectorItem>
|
||||
struct SignalVectorItemArgs {
|
||||
const TVectorItem &item;
|
||||
int index;
|
||||
void *caller;
|
||||
};
|
||||
|
||||
template <typename TVectorItem>
|
||||
class ReadOnlySignalVector : boost::noncopyable
|
||||
{
|
||||
using VecIt = typename std::vector<TVectorItem>::iterator;
|
||||
|
||||
public:
|
||||
struct Iterator
|
||||
: public std::iterator<std::input_iterator_tag, TVectorItem> {
|
||||
Iterator(VecIt &&it, std::shared_mutex &mutex)
|
||||
: it_(std::move(it))
|
||||
, lock_(mutex)
|
||||
, mutex_(mutex)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator(const Iterator &other)
|
||||
: it_(other.it_)
|
||||
, lock_(other.mutex_)
|
||||
, mutex_(other.mutex_)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator &operator=(const Iterator &other)
|
||||
{
|
||||
this->lock_ = std::shared_lock(other.mutex_.get());
|
||||
this->mutex_ = other.mutex_;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
TVectorItem &operator*()
|
||||
{
|
||||
return it_.operator*();
|
||||
}
|
||||
|
||||
Iterator &operator++()
|
||||
{
|
||||
++this->it_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other)
|
||||
{
|
||||
return this->it_ == other.it_;
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator &other)
|
||||
{
|
||||
return this->it_ != other.it_;
|
||||
}
|
||||
|
||||
auto operator-(const Iterator &other)
|
||||
{
|
||||
return this->it_ - other.it_;
|
||||
}
|
||||
|
||||
private:
|
||||
VecIt it_;
|
||||
std::shared_lock<std::shared_mutex> lock_;
|
||||
std::reference_wrapper<std::shared_mutex> mutex_;
|
||||
};
|
||||
|
||||
ReadOnlySignalVector()
|
||||
{
|
||||
QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout,
|
||||
[this] { this->delayedItemsChanged.invoke(); });
|
||||
this->itemsChangedTimer_.setInterval(100);
|
||||
this->itemsChangedTimer_.setSingleShot(true);
|
||||
}
|
||||
virtual ~ReadOnlySignalVector() = default;
|
||||
|
||||
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemInserted;
|
||||
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemRemoved;
|
||||
pajlada::Signals::NoArgSignal delayedItemsChanged;
|
||||
|
||||
Iterator begin() const
|
||||
{
|
||||
return Iterator(
|
||||
const_cast<std::vector<TVectorItem> &>(this->vector_).begin(),
|
||||
this->mutex_);
|
||||
}
|
||||
|
||||
Iterator end() const
|
||||
{
|
||||
return Iterator(
|
||||
const_cast<std::vector<TVectorItem> &>(this->vector_).end(),
|
||||
this->mutex_);
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
std::shared_lock lock(this->mutex_);
|
||||
|
||||
return this->vector_.empty();
|
||||
}
|
||||
|
||||
const std::vector<TVectorItem> &getVector() const
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
return this->vector_;
|
||||
}
|
||||
|
||||
std::vector<TVectorItem> cloneVector() const
|
||||
{
|
||||
std::shared_lock lock(this->mutex_);
|
||||
|
||||
return this->vector_;
|
||||
}
|
||||
|
||||
void invokeDelayedItemsChanged()
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
if (!this->itemsChangedTimer_.isActive())
|
||||
{
|
||||
this->itemsChangedTimer_.start();
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool isSorted() const = 0;
|
||||
|
||||
protected:
|
||||
std::vector<TVectorItem> vector_;
|
||||
QTimer itemsChangedTimer_;
|
||||
mutable std::shared_mutex mutex_;
|
||||
};
|
||||
|
||||
template <typename TVectorItem>
|
||||
class BaseSignalVector : public ReadOnlySignalVector<TVectorItem>
|
||||
{
|
||||
public:
|
||||
// returns the actual index of the inserted item
|
||||
virtual int insertItem(const TVectorItem &item, int proposedIndex = -1,
|
||||
void *caller = nullptr) = 0;
|
||||
|
||||
void removeItem(int index, void *caller = nullptr)
|
||||
{
|
||||
assertInGuiThread();
|
||||
std::unique_lock lock(this->mutex_);
|
||||
|
||||
assert(index >= 0 && index < int(this->vector_.size()));
|
||||
|
||||
TVectorItem item = this->vector_[index];
|
||||
|
||||
this->vector_.erase(this->vector_.begin() + index);
|
||||
lock.unlock(); // manual unlock
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemRemoved.invoke(args);
|
||||
|
||||
this->invokeDelayedItemsChanged();
|
||||
}
|
||||
|
||||
int appendItem(const TVectorItem &item, void *caller = nullptr)
|
||||
{
|
||||
return this->insertItem(item, -1, caller);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TVectorItem>
|
||||
class UnsortedSignalVector : public BaseSignalVector<TVectorItem>
|
||||
{
|
||||
public:
|
||||
virtual int insertItem(const TVectorItem &item, int index = -1,
|
||||
void *caller = nullptr) override
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
{
|
||||
std::unique_lock lock(this->mutex_);
|
||||
if (index == -1)
|
||||
{
|
||||
index = this->vector_.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(index >= 0 && index <= this->vector_.size());
|
||||
}
|
||||
|
||||
this->vector_.insert(this->vector_.begin() + index, item);
|
||||
}
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemInserted.invoke(args);
|
||||
this->invokeDelayedItemsChanged();
|
||||
return index;
|
||||
}
|
||||
|
||||
virtual bool isSorted() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TVectorItem, typename Compare>
|
||||
class SortedSignalVector : public BaseSignalVector<TVectorItem>
|
||||
{
|
||||
public:
|
||||
virtual int insertItem(const TVectorItem &item, int = -1,
|
||||
void *caller = nullptr) override
|
||||
{
|
||||
assertInGuiThread();
|
||||
int index = -1;
|
||||
|
||||
{
|
||||
std::unique_lock lock(this->mutex_);
|
||||
|
||||
auto it = std::lower_bound(this->vector_.begin(),
|
||||
this->vector_.end(), item, Compare{});
|
||||
index = it - this->vector_.begin();
|
||||
this->vector_.insert(it, item);
|
||||
}
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemInserted.invoke(args);
|
||||
this->invokeDelayedItemsChanged();
|
||||
return index;
|
||||
}
|
||||
|
||||
virtual bool isSorted() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QStandardItemModel>
|
||||
#include <QTimer>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
#include <shared_mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "debug/AssertInGuiThread.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename TVectorItem>
|
||||
struct SignalVectorItemArgs {
|
||||
const TVectorItem &item;
|
||||
int index;
|
||||
void *caller;
|
||||
};
|
||||
|
||||
template <typename TVectorItem>
|
||||
class ReadOnlySignalVector : boost::noncopyable
|
||||
{
|
||||
using VecIt = typename std::vector<TVectorItem>::iterator;
|
||||
|
||||
public:
|
||||
struct Iterator
|
||||
: public std::iterator<std::input_iterator_tag, TVectorItem> {
|
||||
Iterator(VecIt &&it, std::shared_mutex &mutex)
|
||||
: it_(std::move(it))
|
||||
, lock_(mutex)
|
||||
, mutex_(mutex)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator(const Iterator &other)
|
||||
: it_(other.it_)
|
||||
, lock_(other.mutex_)
|
||||
, mutex_(other.mutex_)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator &operator=(const Iterator &other)
|
||||
{
|
||||
this->lock_ = std::shared_lock(other.mutex_.get());
|
||||
this->mutex_ = other.mutex_;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
TVectorItem &operator*()
|
||||
{
|
||||
return it_.operator*();
|
||||
}
|
||||
|
||||
Iterator &operator++()
|
||||
{
|
||||
++this->it_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other)
|
||||
{
|
||||
return this->it_ == other.it_;
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator &other)
|
||||
{
|
||||
return this->it_ != other.it_;
|
||||
}
|
||||
|
||||
auto operator-(const Iterator &other)
|
||||
{
|
||||
return this->it_ - other.it_;
|
||||
}
|
||||
|
||||
private:
|
||||
VecIt it_;
|
||||
std::shared_lock<std::shared_mutex> lock_;
|
||||
std::reference_wrapper<std::shared_mutex> mutex_;
|
||||
};
|
||||
|
||||
ReadOnlySignalVector()
|
||||
{
|
||||
QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout,
|
||||
[this] { this->delayedItemsChanged.invoke(); });
|
||||
this->itemsChangedTimer_.setInterval(100);
|
||||
this->itemsChangedTimer_.setSingleShot(true);
|
||||
}
|
||||
virtual ~ReadOnlySignalVector() = default;
|
||||
|
||||
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemInserted;
|
||||
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemRemoved;
|
||||
pajlada::Signals::NoArgSignal delayedItemsChanged;
|
||||
|
||||
Iterator begin() const
|
||||
{
|
||||
return Iterator(
|
||||
const_cast<std::vector<TVectorItem> &>(this->vector_).begin(),
|
||||
this->mutex_);
|
||||
}
|
||||
|
||||
Iterator end() const
|
||||
{
|
||||
return Iterator(
|
||||
const_cast<std::vector<TVectorItem> &>(this->vector_).end(),
|
||||
this->mutex_);
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
std::shared_lock lock(this->mutex_);
|
||||
|
||||
return this->vector_.empty();
|
||||
}
|
||||
|
||||
const std::vector<TVectorItem> &getVector() const
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
return this->vector_;
|
||||
}
|
||||
|
||||
std::vector<TVectorItem> cloneVector() const
|
||||
{
|
||||
std::shared_lock lock(this->mutex_);
|
||||
|
||||
return this->vector_;
|
||||
}
|
||||
|
||||
void invokeDelayedItemsChanged()
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
if (!this->itemsChangedTimer_.isActive())
|
||||
{
|
||||
this->itemsChangedTimer_.start();
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool isSorted() const = 0;
|
||||
|
||||
protected:
|
||||
std::vector<TVectorItem> vector_;
|
||||
QTimer itemsChangedTimer_;
|
||||
mutable std::shared_mutex mutex_;
|
||||
};
|
||||
|
||||
template <typename TVectorItem>
|
||||
class BaseSignalVector : public ReadOnlySignalVector<TVectorItem>
|
||||
{
|
||||
public:
|
||||
// returns the actual index of the inserted item
|
||||
virtual int insertItem(const TVectorItem &item, int proposedIndex = -1,
|
||||
void *caller = nullptr) = 0;
|
||||
|
||||
void removeItem(int index, void *caller = nullptr)
|
||||
{
|
||||
assertInGuiThread();
|
||||
std::unique_lock lock(this->mutex_);
|
||||
|
||||
assert(index >= 0 && index < int(this->vector_.size()));
|
||||
|
||||
TVectorItem item = this->vector_[index];
|
||||
|
||||
this->vector_.erase(this->vector_.begin() + index);
|
||||
lock.unlock(); // manual unlock
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemRemoved.invoke(args);
|
||||
|
||||
this->invokeDelayedItemsChanged();
|
||||
}
|
||||
|
||||
int appendItem(const TVectorItem &item, void *caller = nullptr)
|
||||
{
|
||||
return this->insertItem(item, -1, caller);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TVectorItem>
|
||||
class UnsortedSignalVector : public BaseSignalVector<TVectorItem>
|
||||
{
|
||||
public:
|
||||
virtual int insertItem(const TVectorItem &item, int index = -1,
|
||||
void *caller = nullptr) override
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
{
|
||||
std::unique_lock lock(this->mutex_);
|
||||
if (index == -1)
|
||||
{
|
||||
index = this->vector_.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(index >= 0 && index <= this->vector_.size());
|
||||
}
|
||||
|
||||
this->vector_.insert(this->vector_.begin() + index, item);
|
||||
}
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemInserted.invoke(args);
|
||||
this->invokeDelayedItemsChanged();
|
||||
return index;
|
||||
}
|
||||
|
||||
virtual bool isSorted() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TVectorItem, typename Compare>
|
||||
class SortedSignalVector : public BaseSignalVector<TVectorItem>
|
||||
{
|
||||
public:
|
||||
virtual int insertItem(const TVectorItem &item, int = -1,
|
||||
void *caller = nullptr) override
|
||||
{
|
||||
assertInGuiThread();
|
||||
int index = -1;
|
||||
|
||||
{
|
||||
std::unique_lock lock(this->mutex_);
|
||||
|
||||
auto it = std::lower_bound(this->vector_.begin(),
|
||||
this->vector_.end(), item, Compare{});
|
||||
index = it - this->vector_.begin();
|
||||
this->vector_.insert(it, item);
|
||||
}
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemInserted.invoke(args);
|
||||
this->invokeDelayedItemsChanged();
|
||||
return index;
|
||||
}
|
||||
|
||||
virtual bool isSorted() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,357 +1,357 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/SignalVector.hpp"
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QStandardItem>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename TVectorItem>
|
||||
class SignalVectorModel : public QAbstractTableModel,
|
||||
pajlada::Signals::SignalHolder
|
||||
{
|
||||
public:
|
||||
SignalVectorModel(int columnCount, QObject *parent = nullptr)
|
||||
: QAbstractTableModel(parent)
|
||||
, columnCount_(columnCount)
|
||||
{
|
||||
for (int i = 0; i < columnCount; i++)
|
||||
{
|
||||
this->headerData_.emplace_back();
|
||||
}
|
||||
}
|
||||
|
||||
void init(BaseSignalVector<TVectorItem> *vec)
|
||||
{
|
||||
this->vector_ = vec;
|
||||
|
||||
auto insert = [this](const SignalVectorItemArgs<TVectorItem> &args) {
|
||||
if (args.caller == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// get row index
|
||||
int index = this->getModelIndexFromVectorIndex(args.index);
|
||||
assert(index >= 0 && index <= this->rows_.size());
|
||||
|
||||
// get row items
|
||||
std::vector<QStandardItem *> row = this->createRow();
|
||||
this->getRowFromItem(args.item, row);
|
||||
|
||||
// insert row
|
||||
index = this->beforeInsert(args.item, row, index);
|
||||
|
||||
this->beginInsertRows(QModelIndex(), index, index);
|
||||
this->rows_.insert(this->rows_.begin() + index,
|
||||
Row(row, args.item));
|
||||
this->endInsertRows();
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
for (const TVectorItem &item : vec->getVector())
|
||||
{
|
||||
SignalVectorItemArgs<TVectorItem> args{item, i++, 0};
|
||||
|
||||
insert(args);
|
||||
}
|
||||
|
||||
this->managedConnect(vec->itemInserted, insert);
|
||||
|
||||
this->managedConnect(vec->itemRemoved, [this](auto args) {
|
||||
if (args.caller == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int row = this->getModelIndexFromVectorIndex(args.index);
|
||||
assert(row >= 0 && row <= this->rows_.size());
|
||||
|
||||
// remove row
|
||||
std::vector<QStandardItem *> items =
|
||||
std::move(this->rows_[row].items);
|
||||
|
||||
this->beginRemoveRows(QModelIndex(), row, row);
|
||||
this->rows_.erase(this->rows_.begin() + row);
|
||||
this->endRemoveRows();
|
||||
|
||||
this->afterRemoved(args.item, items, row);
|
||||
|
||||
for (QStandardItem *item : items)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
});
|
||||
|
||||
this->afterInit();
|
||||
}
|
||||
|
||||
virtual ~SignalVectorModel()
|
||||
{
|
||||
for (Row &row : this->rows_)
|
||||
{
|
||||
for (QStandardItem *item : row.items)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex &parent) const override
|
||||
{
|
||||
return this->rows_.size();
|
||||
}
|
||||
|
||||
int columnCount(const QModelIndex &parent) const override
|
||||
{
|
||||
return this->columnCount_;
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override
|
||||
{
|
||||
int row = index.row(), column = index.column();
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
return rows_[row].items[column]->data(role);
|
||||
}
|
||||
|
||||
bool setData(const QModelIndex &index, const QVariant &value,
|
||||
int role) override
|
||||
{
|
||||
int row = index.row(), column = index.column();
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
Row &rowItem = this->rows_[row];
|
||||
|
||||
rowItem.items[column]->setData(value, role);
|
||||
|
||||
if (rowItem.isCustomRow)
|
||||
{
|
||||
this->customRowSetData(rowItem.items, column, value, role, row);
|
||||
}
|
||||
else
|
||||
{
|
||||
int vecRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector_->removeItem(vecRow, this);
|
||||
|
||||
assert(this->rows_[row].original);
|
||||
TVectorItem item = this->getItemFromRow(
|
||||
this->rows_[row].items, this->rows_[row].original.get());
|
||||
this->vector_->insertItem(item, vecRow, this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role) const override
|
||||
{
|
||||
if (orientation != Qt::Horizontal)
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
auto it = this->headerData_[section].find(role);
|
||||
if (it == this->headerData_[section].end())
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
else
|
||||
{
|
||||
return it.value();
|
||||
}
|
||||
}
|
||||
|
||||
bool setHeaderData(int section, Qt::Orientation orientation,
|
||||
const QVariant &value,
|
||||
int role = Qt::DisplayRole) override
|
||||
{
|
||||
if (orientation != Qt::Horizontal)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this->headerData_[section][role] = value;
|
||||
|
||||
emit this->headerDataChanged(Qt::Horizontal, section, section);
|
||||
return true;
|
||||
}
|
||||
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override
|
||||
{
|
||||
int row = index.row(), column = index.column();
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
return this->rows_[row].items[column]->flags();
|
||||
}
|
||||
|
||||
QStandardItem *getItem(int row, int column)
|
||||
{
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
return rows_[row].items[column];
|
||||
}
|
||||
|
||||
void deleteRow(int row)
|
||||
{
|
||||
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector_->removeItem(signalVectorRow);
|
||||
}
|
||||
|
||||
bool removeRows(int row, int count, const QModelIndex &parent) override
|
||||
{
|
||||
if (count != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(row >= 0 && row < this->rows_.size());
|
||||
|
||||
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector_->removeItem(signalVectorRow);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void afterInit()
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
virtual TVectorItem getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const TVectorItem &original) = 0;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const TVectorItem &item,
|
||||
std::vector<QStandardItem *> &row) = 0;
|
||||
|
||||
virtual int beforeInsert(const TVectorItem &item,
|
||||
std::vector<QStandardItem *> &row,
|
||||
int proposedIndex)
|
||||
{
|
||||
return proposedIndex;
|
||||
}
|
||||
|
||||
virtual void afterRemoved(const TVectorItem &item,
|
||||
std::vector<QStandardItem *> &row, int index)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
||||
int column, const QVariant &value, int role,
|
||||
int rowIndex)
|
||||
{
|
||||
}
|
||||
|
||||
void insertCustomRow(std::vector<QStandardItem *> row, int index)
|
||||
{
|
||||
assert(index >= 0 && index <= this->rows_.size());
|
||||
|
||||
this->beginInsertRows(QModelIndex(), index, index);
|
||||
this->rows_.insert(this->rows_.begin() + index,
|
||||
Row(std::move(row), true));
|
||||
this->endInsertRows();
|
||||
}
|
||||
|
||||
void removeCustomRow(int index)
|
||||
{
|
||||
assert(index >= 0 && index <= this->rows_.size());
|
||||
assert(this->rows_[index].isCustomRow);
|
||||
|
||||
this->beginRemoveRows(QModelIndex(), index, index);
|
||||
this->rows_.erase(this->rows_.begin() + index);
|
||||
this->endRemoveRows();
|
||||
}
|
||||
|
||||
std::vector<QStandardItem *> createRow()
|
||||
{
|
||||
std::vector<QStandardItem *> row;
|
||||
for (int i = 0; i < this->columnCount_; i++)
|
||||
{
|
||||
row.push_back(new QStandardItem());
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
struct Row {
|
||||
std::vector<QStandardItem *> items;
|
||||
boost::optional<TVectorItem> original;
|
||||
bool isCustomRow;
|
||||
|
||||
Row(std::vector<QStandardItem *> _items, bool _isCustomRow = false)
|
||||
: items(std::move(_items))
|
||||
, isCustomRow(_isCustomRow)
|
||||
{
|
||||
}
|
||||
|
||||
Row(std::vector<QStandardItem *> _items, const TVectorItem &_original,
|
||||
bool _isCustomRow = false)
|
||||
: items(std::move(_items))
|
||||
, original(_original)
|
||||
, isCustomRow(_isCustomRow)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<QMap<int, QVariant>> headerData_;
|
||||
BaseSignalVector<TVectorItem> *vector_;
|
||||
std::vector<Row> rows_;
|
||||
|
||||
int columnCount_;
|
||||
|
||||
// returns the related index of the SignalVector
|
||||
int getVectorIndexFromModelIndex(int index)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (auto &row : this->rows_)
|
||||
{
|
||||
if (row.isCustomRow)
|
||||
{
|
||||
index--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == index)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
// returns the related index of the model
|
||||
int getModelIndexFromVectorIndex(int index)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (auto &row : this->rows_)
|
||||
{
|
||||
if (row.isCustomRow)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
if (i == index)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "common/SignalVector.hpp"
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QStandardItem>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename TVectorItem>
|
||||
class SignalVectorModel : public QAbstractTableModel,
|
||||
pajlada::Signals::SignalHolder
|
||||
{
|
||||
public:
|
||||
SignalVectorModel(int columnCount, QObject *parent = nullptr)
|
||||
: QAbstractTableModel(parent)
|
||||
, columnCount_(columnCount)
|
||||
{
|
||||
for (int i = 0; i < columnCount; i++)
|
||||
{
|
||||
this->headerData_.emplace_back();
|
||||
}
|
||||
}
|
||||
|
||||
void init(BaseSignalVector<TVectorItem> *vec)
|
||||
{
|
||||
this->vector_ = vec;
|
||||
|
||||
auto insert = [this](const SignalVectorItemArgs<TVectorItem> &args) {
|
||||
if (args.caller == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// get row index
|
||||
int index = this->getModelIndexFromVectorIndex(args.index);
|
||||
assert(index >= 0 && index <= this->rows_.size());
|
||||
|
||||
// get row items
|
||||
std::vector<QStandardItem *> row = this->createRow();
|
||||
this->getRowFromItem(args.item, row);
|
||||
|
||||
// insert row
|
||||
index = this->beforeInsert(args.item, row, index);
|
||||
|
||||
this->beginInsertRows(QModelIndex(), index, index);
|
||||
this->rows_.insert(this->rows_.begin() + index,
|
||||
Row(row, args.item));
|
||||
this->endInsertRows();
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
for (const TVectorItem &item : vec->getVector())
|
||||
{
|
||||
SignalVectorItemArgs<TVectorItem> args{item, i++, 0};
|
||||
|
||||
insert(args);
|
||||
}
|
||||
|
||||
this->managedConnect(vec->itemInserted, insert);
|
||||
|
||||
this->managedConnect(vec->itemRemoved, [this](auto args) {
|
||||
if (args.caller == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int row = this->getModelIndexFromVectorIndex(args.index);
|
||||
assert(row >= 0 && row <= this->rows_.size());
|
||||
|
||||
// remove row
|
||||
std::vector<QStandardItem *> items =
|
||||
std::move(this->rows_[row].items);
|
||||
|
||||
this->beginRemoveRows(QModelIndex(), row, row);
|
||||
this->rows_.erase(this->rows_.begin() + row);
|
||||
this->endRemoveRows();
|
||||
|
||||
this->afterRemoved(args.item, items, row);
|
||||
|
||||
for (QStandardItem *item : items)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
});
|
||||
|
||||
this->afterInit();
|
||||
}
|
||||
|
||||
virtual ~SignalVectorModel()
|
||||
{
|
||||
for (Row &row : this->rows_)
|
||||
{
|
||||
for (QStandardItem *item : row.items)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex &parent) const override
|
||||
{
|
||||
return this->rows_.size();
|
||||
}
|
||||
|
||||
int columnCount(const QModelIndex &parent) const override
|
||||
{
|
||||
return this->columnCount_;
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override
|
||||
{
|
||||
int row = index.row(), column = index.column();
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
return rows_[row].items[column]->data(role);
|
||||
}
|
||||
|
||||
bool setData(const QModelIndex &index, const QVariant &value,
|
||||
int role) override
|
||||
{
|
||||
int row = index.row(), column = index.column();
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
Row &rowItem = this->rows_[row];
|
||||
|
||||
rowItem.items[column]->setData(value, role);
|
||||
|
||||
if (rowItem.isCustomRow)
|
||||
{
|
||||
this->customRowSetData(rowItem.items, column, value, role, row);
|
||||
}
|
||||
else
|
||||
{
|
||||
int vecRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector_->removeItem(vecRow, this);
|
||||
|
||||
assert(this->rows_[row].original);
|
||||
TVectorItem item = this->getItemFromRow(
|
||||
this->rows_[row].items, this->rows_[row].original.get());
|
||||
this->vector_->insertItem(item, vecRow, this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role) const override
|
||||
{
|
||||
if (orientation != Qt::Horizontal)
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
auto it = this->headerData_[section].find(role);
|
||||
if (it == this->headerData_[section].end())
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
else
|
||||
{
|
||||
return it.value();
|
||||
}
|
||||
}
|
||||
|
||||
bool setHeaderData(int section, Qt::Orientation orientation,
|
||||
const QVariant &value,
|
||||
int role = Qt::DisplayRole) override
|
||||
{
|
||||
if (orientation != Qt::Horizontal)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this->headerData_[section][role] = value;
|
||||
|
||||
emit this->headerDataChanged(Qt::Horizontal, section, section);
|
||||
return true;
|
||||
}
|
||||
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override
|
||||
{
|
||||
int row = index.row(), column = index.column();
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
return this->rows_[row].items[column]->flags();
|
||||
}
|
||||
|
||||
QStandardItem *getItem(int row, int column)
|
||||
{
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
return rows_[row].items[column];
|
||||
}
|
||||
|
||||
void deleteRow(int row)
|
||||
{
|
||||
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector_->removeItem(signalVectorRow);
|
||||
}
|
||||
|
||||
bool removeRows(int row, int count, const QModelIndex &parent) override
|
||||
{
|
||||
if (count != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(row >= 0 && row < this->rows_.size());
|
||||
|
||||
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector_->removeItem(signalVectorRow);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void afterInit()
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
virtual TVectorItem getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const TVectorItem &original) = 0;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const TVectorItem &item,
|
||||
std::vector<QStandardItem *> &row) = 0;
|
||||
|
||||
virtual int beforeInsert(const TVectorItem &item,
|
||||
std::vector<QStandardItem *> &row,
|
||||
int proposedIndex)
|
||||
{
|
||||
return proposedIndex;
|
||||
}
|
||||
|
||||
virtual void afterRemoved(const TVectorItem &item,
|
||||
std::vector<QStandardItem *> &row, int index)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
||||
int column, const QVariant &value, int role,
|
||||
int rowIndex)
|
||||
{
|
||||
}
|
||||
|
||||
void insertCustomRow(std::vector<QStandardItem *> row, int index)
|
||||
{
|
||||
assert(index >= 0 && index <= this->rows_.size());
|
||||
|
||||
this->beginInsertRows(QModelIndex(), index, index);
|
||||
this->rows_.insert(this->rows_.begin() + index,
|
||||
Row(std::move(row), true));
|
||||
this->endInsertRows();
|
||||
}
|
||||
|
||||
void removeCustomRow(int index)
|
||||
{
|
||||
assert(index >= 0 && index <= this->rows_.size());
|
||||
assert(this->rows_[index].isCustomRow);
|
||||
|
||||
this->beginRemoveRows(QModelIndex(), index, index);
|
||||
this->rows_.erase(this->rows_.begin() + index);
|
||||
this->endRemoveRows();
|
||||
}
|
||||
|
||||
std::vector<QStandardItem *> createRow()
|
||||
{
|
||||
std::vector<QStandardItem *> row;
|
||||
for (int i = 0; i < this->columnCount_; i++)
|
||||
{
|
||||
row.push_back(new QStandardItem());
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
struct Row {
|
||||
std::vector<QStandardItem *> items;
|
||||
boost::optional<TVectorItem> original;
|
||||
bool isCustomRow;
|
||||
|
||||
Row(std::vector<QStandardItem *> _items, bool _isCustomRow = false)
|
||||
: items(std::move(_items))
|
||||
, isCustomRow(_isCustomRow)
|
||||
{
|
||||
}
|
||||
|
||||
Row(std::vector<QStandardItem *> _items, const TVectorItem &_original,
|
||||
bool _isCustomRow = false)
|
||||
: items(std::move(_items))
|
||||
, original(_original)
|
||||
, isCustomRow(_isCustomRow)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<QMap<int, QVariant>> headerData_;
|
||||
BaseSignalVector<TVectorItem> *vector_;
|
||||
std::vector<Row> rows_;
|
||||
|
||||
int columnCount_;
|
||||
|
||||
// returns the related index of the SignalVector
|
||||
int getVectorIndexFromModelIndex(int index)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (auto &row : this->rows_)
|
||||
{
|
||||
if (row.isCustomRow)
|
||||
{
|
||||
index--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == index)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
// returns the related index of the model
|
||||
int getModelIndexFromVectorIndex(int index)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (auto &row : this->rows_)
|
||||
{
|
||||
if (row.isCustomRow)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
if (i == index)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,125 +1,125 @@
|
|||
#include "UsernameSet.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
//
|
||||
// UsernameSet
|
||||
//
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::begin() const
|
||||
{
|
||||
return this->items.begin();
|
||||
}
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::end() const
|
||||
{
|
||||
return this->items.end();
|
||||
}
|
||||
|
||||
UsernameSet::Range UsernameSet::subrange(const Prefix &prefix) const
|
||||
{
|
||||
auto it = this->firstKeyForPrefix.find(prefix);
|
||||
if (it != this->firstKeyForPrefix.end())
|
||||
{
|
||||
auto start = this->items.find(it->second);
|
||||
auto end = start;
|
||||
|
||||
while (end != this->items.end() && prefix.isStartOf(*end))
|
||||
{
|
||||
end++;
|
||||
}
|
||||
return {start, end};
|
||||
}
|
||||
|
||||
return {this->items.end(), this->items.end()};
|
||||
}
|
||||
|
||||
std::set<QString>::size_type UsernameSet::size() const
|
||||
{
|
||||
return this->items.size();
|
||||
}
|
||||
|
||||
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(const QString &value)
|
||||
{
|
||||
this->insertPrefix(value);
|
||||
|
||||
return this->items.insert(value);
|
||||
}
|
||||
|
||||
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(QString &&value)
|
||||
{
|
||||
this->insertPrefix(value);
|
||||
|
||||
return this->items.insert(std::move(value));
|
||||
}
|
||||
|
||||
void UsernameSet::insertPrefix(const QString &value)
|
||||
{
|
||||
auto &string = this->firstKeyForPrefix[Prefix(value)];
|
||||
|
||||
if (string.isNull() || value.compare(string, Qt::CaseInsensitive) < 0)
|
||||
string = value;
|
||||
}
|
||||
|
||||
//
|
||||
// Range
|
||||
//
|
||||
|
||||
UsernameSet::Range::Range(ConstIterator start, ConstIterator end)
|
||||
: start_(start)
|
||||
, end_(end)
|
||||
{
|
||||
}
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::Range::begin()
|
||||
{
|
||||
return this->start_;
|
||||
}
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::Range::end()
|
||||
{
|
||||
return this->end_;
|
||||
}
|
||||
|
||||
//
|
||||
// Prefix
|
||||
//
|
||||
|
||||
Prefix::Prefix(const QString &string)
|
||||
: first(string.size() >= 1 ? string[0].toLower() : '\0')
|
||||
, second(string.size() >= 2 ? string[1].toLower() : '\0')
|
||||
{
|
||||
}
|
||||
|
||||
bool Prefix::operator==(const Prefix &other) const
|
||||
{
|
||||
return std::tie(this->first, this->second) ==
|
||||
std::tie(other.first, other.second);
|
||||
}
|
||||
|
||||
bool Prefix::operator!=(const Prefix &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool Prefix::isStartOf(const QString &string) const
|
||||
{
|
||||
if (string.size() == 0)
|
||||
{
|
||||
return this->first == QChar('\0') && this->second == QChar('\0');
|
||||
}
|
||||
else if (string.size() == 1)
|
||||
{
|
||||
return this->first == string[0].toLower() &&
|
||||
this->second == QChar('\0');
|
||||
}
|
||||
else
|
||||
{
|
||||
return this->first == string[0].toLower() &&
|
||||
this->second == string[1].toLower();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "UsernameSet.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
//
|
||||
// UsernameSet
|
||||
//
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::begin() const
|
||||
{
|
||||
return this->items.begin();
|
||||
}
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::end() const
|
||||
{
|
||||
return this->items.end();
|
||||
}
|
||||
|
||||
UsernameSet::Range UsernameSet::subrange(const Prefix &prefix) const
|
||||
{
|
||||
auto it = this->firstKeyForPrefix.find(prefix);
|
||||
if (it != this->firstKeyForPrefix.end())
|
||||
{
|
||||
auto start = this->items.find(it->second);
|
||||
auto end = start;
|
||||
|
||||
while (end != this->items.end() && prefix.isStartOf(*end))
|
||||
{
|
||||
end++;
|
||||
}
|
||||
return {start, end};
|
||||
}
|
||||
|
||||
return {this->items.end(), this->items.end()};
|
||||
}
|
||||
|
||||
std::set<QString>::size_type UsernameSet::size() const
|
||||
{
|
||||
return this->items.size();
|
||||
}
|
||||
|
||||
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(const QString &value)
|
||||
{
|
||||
this->insertPrefix(value);
|
||||
|
||||
return this->items.insert(value);
|
||||
}
|
||||
|
||||
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(QString &&value)
|
||||
{
|
||||
this->insertPrefix(value);
|
||||
|
||||
return this->items.insert(std::move(value));
|
||||
}
|
||||
|
||||
void UsernameSet::insertPrefix(const QString &value)
|
||||
{
|
||||
auto &string = this->firstKeyForPrefix[Prefix(value)];
|
||||
|
||||
if (string.isNull() || value.compare(string, Qt::CaseInsensitive) < 0)
|
||||
string = value;
|
||||
}
|
||||
|
||||
//
|
||||
// Range
|
||||
//
|
||||
|
||||
UsernameSet::Range::Range(ConstIterator start, ConstIterator end)
|
||||
: start_(start)
|
||||
, end_(end)
|
||||
{
|
||||
}
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::Range::begin()
|
||||
{
|
||||
return this->start_;
|
||||
}
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::Range::end()
|
||||
{
|
||||
return this->end_;
|
||||
}
|
||||
|
||||
//
|
||||
// Prefix
|
||||
//
|
||||
|
||||
Prefix::Prefix(const QString &string)
|
||||
: first(string.size() >= 1 ? string[0].toLower() : '\0')
|
||||
, second(string.size() >= 2 ? string[1].toLower() : '\0')
|
||||
{
|
||||
}
|
||||
|
||||
bool Prefix::operator==(const Prefix &other) const
|
||||
{
|
||||
return std::tie(this->first, this->second) ==
|
||||
std::tie(other.first, other.second);
|
||||
}
|
||||
|
||||
bool Prefix::operator!=(const Prefix &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool Prefix::isStartOf(const QString &string) const
|
||||
{
|
||||
if (string.size() == 0)
|
||||
{
|
||||
return this->first == QChar('\0') && this->second == QChar('\0');
|
||||
}
|
||||
else if (string.size() == 1)
|
||||
{
|
||||
return this->first == string[0].toLower() &&
|
||||
this->second == QChar('\0');
|
||||
}
|
||||
else
|
||||
{
|
||||
return this->first == string[0].toLower() &&
|
||||
this->second == string[1].toLower();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,86 +1,86 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Prefix
|
||||
{
|
||||
public:
|
||||
Prefix(const QString &string);
|
||||
bool operator==(const Prefix &other) const;
|
||||
bool operator!=(const Prefix &other) const;
|
||||
bool isStartOf(const QString &string) const;
|
||||
|
||||
private:
|
||||
QChar first;
|
||||
QChar second;
|
||||
|
||||
friend struct std::hash<Prefix>;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<chatterino::Prefix> {
|
||||
size_t operator()(const chatterino::Prefix &prefix) const
|
||||
{
|
||||
return (size_t(prefix.first.unicode()) << 16) |
|
||||
size_t(prefix.second.unicode());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CaseInsensitiveLess {
|
||||
bool operator()(const QString &lhs, const QString &rhs) const
|
||||
{
|
||||
return lhs.compare(rhs, Qt::CaseInsensitive) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
class UsernameSet
|
||||
{
|
||||
public:
|
||||
static constexpr int PrefixLength = 2;
|
||||
|
||||
using Iterator = std::set<QString>::iterator;
|
||||
using ConstIterator = std::set<QString>::const_iterator;
|
||||
|
||||
class Range
|
||||
{
|
||||
public:
|
||||
Range(ConstIterator start, ConstIterator end);
|
||||
|
||||
ConstIterator begin();
|
||||
ConstIterator end();
|
||||
|
||||
private:
|
||||
ConstIterator start_;
|
||||
ConstIterator end_;
|
||||
};
|
||||
|
||||
ConstIterator begin() const;
|
||||
ConstIterator end() const;
|
||||
Range subrange(const Prefix &prefix) const;
|
||||
|
||||
std::set<QString>::size_type size() const;
|
||||
|
||||
std::pair<Iterator, bool> insert(const QString &value);
|
||||
std::pair<Iterator, bool> insert(QString &&value);
|
||||
|
||||
private:
|
||||
void insertPrefix(const QString &string);
|
||||
|
||||
std::set<QString, CaseInsensitiveLess> items;
|
||||
std::unordered_map<Prefix, QString> firstKeyForPrefix;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Prefix
|
||||
{
|
||||
public:
|
||||
Prefix(const QString &string);
|
||||
bool operator==(const Prefix &other) const;
|
||||
bool operator!=(const Prefix &other) const;
|
||||
bool isStartOf(const QString &string) const;
|
||||
|
||||
private:
|
||||
QChar first;
|
||||
QChar second;
|
||||
|
||||
friend struct std::hash<Prefix>;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<chatterino::Prefix> {
|
||||
size_t operator()(const chatterino::Prefix &prefix) const
|
||||
{
|
||||
return (size_t(prefix.first.unicode()) << 16) |
|
||||
size_t(prefix.second.unicode());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CaseInsensitiveLess {
|
||||
bool operator()(const QString &lhs, const QString &rhs) const
|
||||
{
|
||||
return lhs.compare(rhs, Qt::CaseInsensitive) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
class UsernameSet
|
||||
{
|
||||
public:
|
||||
static constexpr int PrefixLength = 2;
|
||||
|
||||
using Iterator = std::set<QString>::iterator;
|
||||
using ConstIterator = std::set<QString>::const_iterator;
|
||||
|
||||
class Range
|
||||
{
|
||||
public:
|
||||
Range(ConstIterator start, ConstIterator end);
|
||||
|
||||
ConstIterator begin();
|
||||
ConstIterator end();
|
||||
|
||||
private:
|
||||
ConstIterator start_;
|
||||
ConstIterator end_;
|
||||
};
|
||||
|
||||
ConstIterator begin() const;
|
||||
ConstIterator end() const;
|
||||
Range subrange(const Prefix &prefix) const;
|
||||
|
||||
std::set<QString>::size_type size() const;
|
||||
|
||||
std::pair<Iterator, bool> insert(const QString &value);
|
||||
std::pair<Iterator, bool> insert(QString &&value);
|
||||
|
||||
private:
|
||||
void insertPrefix(const QString &string);
|
||||
|
||||
std::set<QString, CaseInsensitiveLess> items;
|
||||
std::unordered_map<Prefix, QString> firstKeyForPrefix;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#define CHATTERINO_VERSION "2.1.4-beta-2"
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
# define CHATTERINO_OS "win"
|
||||
#elif defined(Q_OS_MACOS)
|
||||
# define CHATTERINO_OS "macos"
|
||||
#elif defined(Q_OS_LINUX)
|
||||
# define CHATTERINO_OS "linux"
|
||||
#else
|
||||
# define CHATTERINO_OS "unknown"
|
||||
#endif
|
||||
#pragma once
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#define CHATTERINO_VERSION "2.1.4-beta-2"
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
# define CHATTERINO_OS "win"
|
||||
#elif defined(Q_OS_MACOS)
|
||||
# define CHATTERINO_OS "macos"
|
||||
#elif defined(Q_OS_LINUX)
|
||||
# define CHATTERINO_OS "linux"
|
||||
#else
|
||||
# define CHATTERINO_OS "unknown"
|
||||
#endif
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
#include "Account.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
Account::Account(ProviderId providerId)
|
||||
: providerId_(providerId)
|
||||
{
|
||||
static QString twitch("Twitch");
|
||||
|
||||
this->category_ = [&]() {
|
||||
switch (providerId)
|
||||
{
|
||||
case ProviderId::Twitch:
|
||||
return twitch;
|
||||
}
|
||||
return QString("Unknown ProviderId");
|
||||
}();
|
||||
}
|
||||
|
||||
const QString &Account::getCategory() const
|
||||
{
|
||||
return this->category_;
|
||||
}
|
||||
|
||||
ProviderId Account::getProviderId() const
|
||||
{
|
||||
return this->providerId_;
|
||||
}
|
||||
|
||||
bool Account::operator<(const Account &other) const
|
||||
{
|
||||
QString a = this->toString();
|
||||
QString b = other.toString();
|
||||
|
||||
return std::tie(this->category_, a) < std::tie(other.category_, b);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "Account.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
Account::Account(ProviderId providerId)
|
||||
: providerId_(providerId)
|
||||
{
|
||||
static QString twitch("Twitch");
|
||||
|
||||
this->category_ = [&]() {
|
||||
switch (providerId)
|
||||
{
|
||||
case ProviderId::Twitch:
|
||||
return twitch;
|
||||
}
|
||||
return QString("Unknown ProviderId");
|
||||
}();
|
||||
}
|
||||
|
||||
const QString &Account::getCategory() const
|
||||
{
|
||||
return this->category_;
|
||||
}
|
||||
|
||||
ProviderId Account::getProviderId() const
|
||||
{
|
||||
return this->providerId_;
|
||||
}
|
||||
|
||||
bool Account::operator<(const Account &other) const
|
||||
{
|
||||
QString a = this->toString();
|
||||
QString b = other.toString();
|
||||
|
||||
return std::tie(this->category_, a) < std::tie(other.category_, b);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/ProviderId.hpp"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Account
|
||||
{
|
||||
public:
|
||||
Account(ProviderId providerId);
|
||||
virtual ~Account() = default;
|
||||
|
||||
virtual QString toString() const = 0;
|
||||
const QString &getCategory() const;
|
||||
ProviderId getProviderId() const;
|
||||
|
||||
bool operator<(const Account &other) const;
|
||||
|
||||
private:
|
||||
ProviderId providerId_;
|
||||
QString category_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "common/ProviderId.hpp"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Account
|
||||
{
|
||||
public:
|
||||
Account(ProviderId providerId);
|
||||
virtual ~Account() = default;
|
||||
|
||||
virtual QString toString() const = 0;
|
||||
const QString &getCategory() const;
|
||||
ProviderId getProviderId() const;
|
||||
|
||||
bool operator<(const Account &other) const;
|
||||
|
||||
private:
|
||||
ProviderId providerId_;
|
||||
QString category_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,58 +1,58 @@
|
|||
#include "AccountController.hpp"
|
||||
|
||||
#include "controllers/accounts/Account.hpp"
|
||||
#include "controllers/accounts/AccountModel.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
AccountController::AccountController()
|
||||
{
|
||||
this->twitch.accounts.itemInserted.connect([this](const auto &args) {
|
||||
this->accounts_.insertItem(
|
||||
std::dynamic_pointer_cast<Account>(args.item));
|
||||
});
|
||||
|
||||
this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
|
||||
if (args.caller != this)
|
||||
{
|
||||
auto &accs = this->twitch.accounts.getVector();
|
||||
auto it = std::find(accs.begin(), accs.end(), args.item);
|
||||
assert(it != accs.end());
|
||||
|
||||
this->accounts_.removeItem(it - accs.begin(), this);
|
||||
}
|
||||
});
|
||||
|
||||
this->accounts_.itemRemoved.connect([this](const auto &args) {
|
||||
switch (args.item->getProviderId())
|
||||
{
|
||||
case ProviderId::Twitch:
|
||||
{
|
||||
if (args.caller != this)
|
||||
{
|
||||
auto accs = this->twitch.accounts.cloneVector();
|
||||
auto it = std::find(accs.begin(), accs.end(), args.item);
|
||||
assert(it != accs.end());
|
||||
this->twitch.accounts.removeItem(it - accs.begin(), this);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void AccountController::initialize(Settings &settings, Paths &paths)
|
||||
{
|
||||
this->twitch.load();
|
||||
}
|
||||
|
||||
AccountModel *AccountController::createModel(QObject *parent)
|
||||
{
|
||||
AccountModel *model = new AccountModel(parent);
|
||||
|
||||
model->init(&this->accounts_);
|
||||
return model;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "AccountController.hpp"
|
||||
|
||||
#include "controllers/accounts/Account.hpp"
|
||||
#include "controllers/accounts/AccountModel.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
AccountController::AccountController()
|
||||
{
|
||||
this->twitch.accounts.itemInserted.connect([this](const auto &args) {
|
||||
this->accounts_.insertItem(
|
||||
std::dynamic_pointer_cast<Account>(args.item));
|
||||
});
|
||||
|
||||
this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
|
||||
if (args.caller != this)
|
||||
{
|
||||
auto &accs = this->twitch.accounts.getVector();
|
||||
auto it = std::find(accs.begin(), accs.end(), args.item);
|
||||
assert(it != accs.end());
|
||||
|
||||
this->accounts_.removeItem(it - accs.begin(), this);
|
||||
}
|
||||
});
|
||||
|
||||
this->accounts_.itemRemoved.connect([this](const auto &args) {
|
||||
switch (args.item->getProviderId())
|
||||
{
|
||||
case ProviderId::Twitch:
|
||||
{
|
||||
if (args.caller != this)
|
||||
{
|
||||
auto accs = this->twitch.accounts.cloneVector();
|
||||
auto it = std::find(accs.begin(), accs.end(), args.item);
|
||||
assert(it != accs.end());
|
||||
this->twitch.accounts.removeItem(it - accs.begin(), this);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void AccountController::initialize(Settings &settings, Paths &paths)
|
||||
{
|
||||
this->twitch.load();
|
||||
}
|
||||
|
||||
AccountModel *AccountController::createModel(QObject *parent)
|
||||
{
|
||||
AccountModel *model = new AccountModel(parent);
|
||||
|
||||
model->init(&this->accounts_);
|
||||
return model;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "common/SignalVector.hpp"
|
||||
#include "common/Singleton.hpp"
|
||||
#include "providers/twitch/TwitchAccountManager.hpp"
|
||||
#include "util/SharedPtrElementLess.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Account;
|
||||
class Settings;
|
||||
class Paths;
|
||||
|
||||
class AccountModel;
|
||||
|
||||
class AccountController final : public Singleton
|
||||
{
|
||||
public:
|
||||
AccountController();
|
||||
|
||||
AccountModel *createModel(QObject *parent);
|
||||
|
||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
||||
|
||||
TwitchAccountManager twitch;
|
||||
|
||||
private:
|
||||
SortedSignalVector<std::shared_ptr<Account>, SharedPtrElementLess<Account>>
|
||||
accounts_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "common/SignalVector.hpp"
|
||||
#include "common/Singleton.hpp"
|
||||
#include "providers/twitch/TwitchAccountManager.hpp"
|
||||
#include "util/SharedPtrElementLess.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Account;
|
||||
class Settings;
|
||||
class Paths;
|
||||
|
||||
class AccountModel;
|
||||
|
||||
class AccountController final : public Singleton
|
||||
{
|
||||
public:
|
||||
AccountController();
|
||||
|
||||
AccountModel *createModel(QObject *parent);
|
||||
|
||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
||||
|
||||
TwitchAccountManager twitch;
|
||||
|
||||
private:
|
||||
SortedSignalVector<std::shared_ptr<Account>, SharedPtrElementLess<Account>>
|
||||
accounts_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,64 +1,64 @@
|
|||
#include "AccountModel.hpp"
|
||||
|
||||
#include "controllers/accounts/Account.hpp"
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
AccountModel::AccountModel(QObject *parent)
|
||||
: SignalVectorModel<std::shared_ptr<Account>>(1, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
std::shared_ptr<Account> AccountModel::getItemFromRow(
|
||||
std::vector<QStandardItem *> &, const std::shared_ptr<Account> &original)
|
||||
{
|
||||
return original;
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void AccountModel::getRowFromItem(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item->toString(), false);
|
||||
row[0]->setData(QFont("Segoe UI", 10), Qt::FontRole);
|
||||
}
|
||||
|
||||
int AccountModel::beforeInsert(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row,
|
||||
int proposedIndex)
|
||||
{
|
||||
if (this->categoryCount_[item->getCategory()]++ == 0)
|
||||
{
|
||||
auto newRow = this->createRow();
|
||||
|
||||
setStringItem(newRow[0], item->getCategory(), false, false);
|
||||
newRow[0]->setData(QFont("Segoe UI Light", 16), Qt::FontRole);
|
||||
|
||||
this->insertCustomRow(std::move(newRow), proposedIndex);
|
||||
|
||||
return proposedIndex + 1;
|
||||
}
|
||||
|
||||
return proposedIndex;
|
||||
}
|
||||
|
||||
void AccountModel::afterRemoved(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row, int index)
|
||||
{
|
||||
auto it = this->categoryCount_.find(item->getCategory());
|
||||
assert(it != this->categoryCount_.end());
|
||||
|
||||
if (it->second <= 1)
|
||||
{
|
||||
this->categoryCount_.erase(it);
|
||||
this->removeCustomRow(index - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
it->second--;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "AccountModel.hpp"
|
||||
|
||||
#include "controllers/accounts/Account.hpp"
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
AccountModel::AccountModel(QObject *parent)
|
||||
: SignalVectorModel<std::shared_ptr<Account>>(1, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
std::shared_ptr<Account> AccountModel::getItemFromRow(
|
||||
std::vector<QStandardItem *> &, const std::shared_ptr<Account> &original)
|
||||
{
|
||||
return original;
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void AccountModel::getRowFromItem(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item->toString(), false);
|
||||
row[0]->setData(QFont("Segoe UI", 10), Qt::FontRole);
|
||||
}
|
||||
|
||||
int AccountModel::beforeInsert(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row,
|
||||
int proposedIndex)
|
||||
{
|
||||
if (this->categoryCount_[item->getCategory()]++ == 0)
|
||||
{
|
||||
auto newRow = this->createRow();
|
||||
|
||||
setStringItem(newRow[0], item->getCategory(), false, false);
|
||||
newRow[0]->setData(QFont("Segoe UI Light", 16), Qt::FontRole);
|
||||
|
||||
this->insertCustomRow(std::move(newRow), proposedIndex);
|
||||
|
||||
return proposedIndex + 1;
|
||||
}
|
||||
|
||||
return proposedIndex;
|
||||
}
|
||||
|
||||
void AccountModel::afterRemoved(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row, int index)
|
||||
{
|
||||
auto it = this->categoryCount_.find(item->getCategory());
|
||||
assert(it != this->categoryCount_.end());
|
||||
|
||||
if (it->second <= 1)
|
||||
{
|
||||
this->categoryCount_.erase(it);
|
||||
this->removeCustomRow(index - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
it->second--;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "controllers/accounts/Account.hpp"
|
||||
#include "util/QStringHash.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Account;
|
||||
class AccountController;
|
||||
|
||||
class AccountModel : public SignalVectorModel<std::shared_ptr<Account>>
|
||||
{
|
||||
public:
|
||||
AccountModel(QObject *parent);
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual std::shared_ptr<Account> getItemFromRow(
|
||||
std::vector<QStandardItem *> &row,
|
||||
const std::shared_ptr<Account> &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row) override;
|
||||
|
||||
virtual int beforeInsert(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row,
|
||||
int proposedIndex) override;
|
||||
|
||||
virtual void afterRemoved(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row,
|
||||
int index) override;
|
||||
|
||||
friend class AccountController;
|
||||
|
||||
private:
|
||||
std::unordered_map<QString, int> categoryCount_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "controllers/accounts/Account.hpp"
|
||||
#include "util/QStringHash.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Account;
|
||||
class AccountController;
|
||||
|
||||
class AccountModel : public SignalVectorModel<std::shared_ptr<Account>>
|
||||
{
|
||||
public:
|
||||
AccountModel(QObject *parent);
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual std::shared_ptr<Account> getItemFromRow(
|
||||
std::vector<QStandardItem *> &row,
|
||||
const std::shared_ptr<Account> &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row) override;
|
||||
|
||||
virtual int beforeInsert(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row,
|
||||
int proposedIndex) override;
|
||||
|
||||
virtual void afterRemoved(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row,
|
||||
int index) override;
|
||||
|
||||
friend class AccountController;
|
||||
|
||||
private:
|
||||
std::unordered_map<QString, int> categoryCount_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
#include "Command.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// command
|
||||
Command::Command(const QString &_text)
|
||||
{
|
||||
int index = _text.indexOf(' ');
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
this->name = _text;
|
||||
return;
|
||||
}
|
||||
|
||||
this->name = _text.mid(0, index).trimmed();
|
||||
this->func = _text.mid(index + 1).trimmed();
|
||||
}
|
||||
|
||||
Command::Command(const QString &_name, const QString &_func)
|
||||
: name(_name.trimmed())
|
||||
, func(_func.trimmed())
|
||||
{
|
||||
}
|
||||
|
||||
QString Command::toString() const
|
||||
{
|
||||
return this->name + " " + this->func;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "Command.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// command
|
||||
Command::Command(const QString &_text)
|
||||
{
|
||||
int index = _text.indexOf(' ');
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
this->name = _text;
|
||||
return;
|
||||
}
|
||||
|
||||
this->name = _text.mid(0, index).trimmed();
|
||||
this->func = _text.mid(index + 1).trimmed();
|
||||
}
|
||||
|
||||
Command::Command(const QString &_name, const QString &_func)
|
||||
: name(_name.trimmed())
|
||||
, func(_func.trimmed())
|
||||
{
|
||||
}
|
||||
|
||||
QString Command::toString() const
|
||||
{
|
||||
return this->name + " " + this->func;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,67 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <pajlada/serialize.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct Command {
|
||||
QString name;
|
||||
QString func;
|
||||
|
||||
Command() = default;
|
||||
explicit Command(const QString &text);
|
||||
Command(const QString &name, const QString &func);
|
||||
|
||||
QString toString() const;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace pajlada {
|
||||
|
||||
template <>
|
||||
struct Serialize<chatterino::Command> {
|
||||
static rapidjson::Value get(const chatterino::Command &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
rapidjson::Value ret(rapidjson::kObjectType);
|
||||
|
||||
chatterino::rj::set(ret, "name", value.name, a);
|
||||
chatterino::rj::set(ret, "func", value.func, a);
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Deserialize<chatterino::Command> {
|
||||
static chatterino::Command get(const rapidjson::Value &value,
|
||||
bool *error = nullptr)
|
||||
{
|
||||
chatterino::Command command;
|
||||
|
||||
if (!value.IsObject())
|
||||
{
|
||||
PAJLADA_REPORT_ERROR(error);
|
||||
return command;
|
||||
}
|
||||
|
||||
if (!chatterino::rj::getSafe(value, "name", command.name))
|
||||
{
|
||||
PAJLADA_REPORT_ERROR(error);
|
||||
return command;
|
||||
}
|
||||
if (!chatterino::rj::getSafe(value, "func", command.func))
|
||||
{
|
||||
PAJLADA_REPORT_ERROR(error);
|
||||
return command;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace pajlada
|
||||
#pragma once
|
||||
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <pajlada/serialize.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct Command {
|
||||
QString name;
|
||||
QString func;
|
||||
|
||||
Command() = default;
|
||||
explicit Command(const QString &text);
|
||||
Command(const QString &name, const QString &func);
|
||||
|
||||
QString toString() const;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace pajlada {
|
||||
|
||||
template <>
|
||||
struct Serialize<chatterino::Command> {
|
||||
static rapidjson::Value get(const chatterino::Command &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
rapidjson::Value ret(rapidjson::kObjectType);
|
||||
|
||||
chatterino::rj::set(ret, "name", value.name, a);
|
||||
chatterino::rj::set(ret, "func", value.func, a);
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Deserialize<chatterino::Command> {
|
||||
static chatterino::Command get(const rapidjson::Value &value,
|
||||
bool *error = nullptr)
|
||||
{
|
||||
chatterino::Command command;
|
||||
|
||||
if (!value.IsObject())
|
||||
{
|
||||
PAJLADA_REPORT_ERROR(error);
|
||||
return command;
|
||||
}
|
||||
|
||||
if (!chatterino::rj::getSafe(value, "name", command.name))
|
||||
{
|
||||
PAJLADA_REPORT_ERROR(error);
|
||||
return command;
|
||||
}
|
||||
if (!chatterino::rj::getSafe(value, "func", command.func))
|
||||
{
|
||||
PAJLADA_REPORT_ERROR(error);
|
||||
return command;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace pajlada
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,56 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/ChatterinoSetting.hpp"
|
||||
#include "common/SignalVector.hpp"
|
||||
#include "common/Singleton.hpp"
|
||||
#include "controllers/commands/Command.hpp"
|
||||
|
||||
#include <QMap>
|
||||
#include <pajlada/settings.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Settings;
|
||||
class Paths;
|
||||
class Channel;
|
||||
|
||||
class CommandModel;
|
||||
|
||||
class CommandController final : public Singleton
|
||||
{
|
||||
public:
|
||||
UnsortedSignalVector<Command> items_;
|
||||
|
||||
QString execCommand(const QString &text, std::shared_ptr<Channel> channel,
|
||||
bool dryRun);
|
||||
QStringList getDefaultTwitchCommandList();
|
||||
|
||||
virtual void initialize(Settings &, Paths &paths) override;
|
||||
virtual void save() override;
|
||||
|
||||
CommandModel *createModel(QObject *parent);
|
||||
|
||||
private:
|
||||
void load(Paths &paths);
|
||||
|
||||
QMap<QString, Command> commandsMap_;
|
||||
int maxSpaces_ = 0;
|
||||
|
||||
std::mutex mutex_;
|
||||
|
||||
std::shared_ptr<pajlada::Settings::SettingManager> sm_;
|
||||
// Because the setting manager is not initialized until the initialize
|
||||
// function is called (and not in the constructor), we have to
|
||||
// late-initialize the setting, which is why we're storing it as a
|
||||
// unique_ptr
|
||||
std::unique_ptr<pajlada::Settings::Setting<std::vector<Command>>>
|
||||
commandsSetting_;
|
||||
|
||||
QString execCustomCommand(const QStringList &words, const Command &command,
|
||||
bool dryRun);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "common/ChatterinoSetting.hpp"
|
||||
#include "common/SignalVector.hpp"
|
||||
#include "common/Singleton.hpp"
|
||||
#include "controllers/commands/Command.hpp"
|
||||
|
||||
#include <QMap>
|
||||
#include <pajlada/settings.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Settings;
|
||||
class Paths;
|
||||
class Channel;
|
||||
|
||||
class CommandModel;
|
||||
|
||||
class CommandController final : public Singleton
|
||||
{
|
||||
public:
|
||||
UnsortedSignalVector<Command> items_;
|
||||
|
||||
QString execCommand(const QString &text, std::shared_ptr<Channel> channel,
|
||||
bool dryRun);
|
||||
QStringList getDefaultTwitchCommandList();
|
||||
|
||||
virtual void initialize(Settings &, Paths &paths) override;
|
||||
virtual void save() override;
|
||||
|
||||
CommandModel *createModel(QObject *parent);
|
||||
|
||||
private:
|
||||
void load(Paths &paths);
|
||||
|
||||
QMap<QString, Command> commandsMap_;
|
||||
int maxSpaces_ = 0;
|
||||
|
||||
std::mutex mutex_;
|
||||
|
||||
std::shared_ptr<pajlada::Settings::SettingManager> sm_;
|
||||
// Because the setting manager is not initialized until the initialize
|
||||
// function is called (and not in the constructor), we have to
|
||||
// late-initialize the setting, which is why we're storing it as a
|
||||
// unique_ptr
|
||||
std::unique_ptr<pajlada::Settings::Setting<std::vector<Command>>>
|
||||
commandsSetting_;
|
||||
|
||||
QString execCustomCommand(const QStringList &words, const Command &command,
|
||||
bool dryRun);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
#include "CommandModel.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// commandmodel
|
||||
CommandModel::CommandModel(QObject *parent)
|
||||
: SignalVectorModel<Command>(2, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
Command CommandModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const Command &original)
|
||||
{
|
||||
return Command(row[0]->data(Qt::EditRole).toString(),
|
||||
row[1]->data(Qt::EditRole).toString());
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void CommandModel::getRowFromItem(const Command &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
row[0]->setData(item.name, Qt::DisplayRole);
|
||||
row[0]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable |
|
||||
Qt::ItemIsEditable);
|
||||
row[1]->setData(item.func, Qt::DisplayRole);
|
||||
row[1]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable |
|
||||
Qt::ItemIsEditable);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "CommandModel.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// commandmodel
|
||||
CommandModel::CommandModel(QObject *parent)
|
||||
: SignalVectorModel<Command>(2, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
Command CommandModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const Command &original)
|
||||
{
|
||||
return Command(row[0]->data(Qt::EditRole).toString(),
|
||||
row[1]->data(Qt::EditRole).toString());
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void CommandModel::getRowFromItem(const Command &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
row[0]->setData(item.name, Qt::DisplayRole);
|
||||
row[0]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable |
|
||||
Qt::ItemIsEditable);
|
||||
row[1]->setData(item.func, Qt::DisplayRole);
|
||||
row[1]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable |
|
||||
Qt::ItemIsEditable);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "controllers/commands/Command.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class CommandController;
|
||||
|
||||
class CommandModel : public SignalVectorModel<Command>
|
||||
{
|
||||
explicit CommandModel(QObject *parent);
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual Command getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const Command &command) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const Command &item,
|
||||
std::vector<QStandardItem *> &row) override;
|
||||
|
||||
friend class CommandController;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "controllers/commands/Command.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class CommandController;
|
||||
|
||||
class CommandModel : public SignalVectorModel<Command>
|
||||
{
|
||||
explicit CommandModel(QObject *parent);
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual Command getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const Command &command) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const Command &item,
|
||||
std::vector<QStandardItem *> &row) override;
|
||||
|
||||
friend class CommandController;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,110 +1,110 @@
|
|||
#include "HighlightController.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/highlights/HighlightBlacklistModel.hpp"
|
||||
#include "controllers/highlights/HighlightModel.hpp"
|
||||
#include "controllers/highlights/UserHighlightModel.hpp"
|
||||
#include "widgets/dialogs/NotificationPopup.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
HighlightController::HighlightController()
|
||||
{
|
||||
}
|
||||
|
||||
void HighlightController::initialize(Settings &settings, Paths &paths)
|
||||
{
|
||||
assert(!this->initialized_);
|
||||
this->initialized_ = true;
|
||||
|
||||
for (const HighlightPhrase &phrase : this->highlightsSetting_.getValue())
|
||||
{
|
||||
this->phrases.appendItem(phrase);
|
||||
}
|
||||
|
||||
this->phrases.delayedItemsChanged.connect([this] { //
|
||||
this->highlightsSetting_.setValue(this->phrases.getVector());
|
||||
});
|
||||
|
||||
for (const HighlightBlacklistUser &blacklistedUser :
|
||||
this->blacklistSetting_.getValue())
|
||||
{
|
||||
this->blacklistedUsers.appendItem(blacklistedUser);
|
||||
}
|
||||
|
||||
this->blacklistedUsers.delayedItemsChanged.connect([this] {
|
||||
this->blacklistSetting_.setValue(this->blacklistedUsers.getVector());
|
||||
});
|
||||
|
||||
for (const HighlightPhrase &user : this->userSetting_.getValue())
|
||||
{
|
||||
this->highlightedUsers.appendItem(user);
|
||||
}
|
||||
|
||||
this->highlightedUsers.delayedItemsChanged.connect([this] { //
|
||||
this->userSetting_.setValue(this->highlightedUsers.getVector());
|
||||
});
|
||||
}
|
||||
|
||||
HighlightModel *HighlightController::createModel(QObject *parent)
|
||||
{
|
||||
HighlightModel *model = new HighlightModel(parent);
|
||||
model->init(&this->phrases);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
UserHighlightModel *HighlightController::createUserModel(QObject *parent)
|
||||
{
|
||||
auto *model = new UserHighlightModel(parent);
|
||||
model->init(&this->highlightedUsers);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
bool HighlightController::isHighlightedUser(const QString &username)
|
||||
{
|
||||
const auto &userItems = this->highlightedUsers;
|
||||
for (const auto &highlightedUser : userItems)
|
||||
{
|
||||
if (highlightedUser.isMatch(username))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
HighlightBlacklistModel *HighlightController::createBlacklistModel(
|
||||
QObject *parent)
|
||||
{
|
||||
auto *model = new HighlightBlacklistModel(parent);
|
||||
model->init(&this->blacklistedUsers);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
bool HighlightController::blacklistContains(const QString &username)
|
||||
{
|
||||
for (const auto &blacklistedUser : this->blacklistedUsers)
|
||||
{
|
||||
if (blacklistedUser.isMatch(username))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HighlightController::addHighlight(const MessagePtr &msg)
|
||||
{
|
||||
// static NotificationPopup popup;
|
||||
|
||||
// popup.updatePosition();
|
||||
// popup.addMessage(msg);
|
||||
// popup.show();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "HighlightController.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/highlights/HighlightBlacklistModel.hpp"
|
||||
#include "controllers/highlights/HighlightModel.hpp"
|
||||
#include "controllers/highlights/UserHighlightModel.hpp"
|
||||
#include "widgets/dialogs/NotificationPopup.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
HighlightController::HighlightController()
|
||||
{
|
||||
}
|
||||
|
||||
void HighlightController::initialize(Settings &settings, Paths &paths)
|
||||
{
|
||||
assert(!this->initialized_);
|
||||
this->initialized_ = true;
|
||||
|
||||
for (const HighlightPhrase &phrase : this->highlightsSetting_.getValue())
|
||||
{
|
||||
this->phrases.appendItem(phrase);
|
||||
}
|
||||
|
||||
this->phrases.delayedItemsChanged.connect([this] { //
|
||||
this->highlightsSetting_.setValue(this->phrases.getVector());
|
||||
});
|
||||
|
||||
for (const HighlightBlacklistUser &blacklistedUser :
|
||||
this->blacklistSetting_.getValue())
|
||||
{
|
||||
this->blacklistedUsers.appendItem(blacklistedUser);
|
||||
}
|
||||
|
||||
this->blacklistedUsers.delayedItemsChanged.connect([this] {
|
||||
this->blacklistSetting_.setValue(this->blacklistedUsers.getVector());
|
||||
});
|
||||
|
||||
for (const HighlightPhrase &user : this->userSetting_.getValue())
|
||||
{
|
||||
this->highlightedUsers.appendItem(user);
|
||||
}
|
||||
|
||||
this->highlightedUsers.delayedItemsChanged.connect([this] { //
|
||||
this->userSetting_.setValue(this->highlightedUsers.getVector());
|
||||
});
|
||||
}
|
||||
|
||||
HighlightModel *HighlightController::createModel(QObject *parent)
|
||||
{
|
||||
HighlightModel *model = new HighlightModel(parent);
|
||||
model->init(&this->phrases);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
UserHighlightModel *HighlightController::createUserModel(QObject *parent)
|
||||
{
|
||||
auto *model = new UserHighlightModel(parent);
|
||||
model->init(&this->highlightedUsers);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
bool HighlightController::isHighlightedUser(const QString &username)
|
||||
{
|
||||
const auto &userItems = this->highlightedUsers;
|
||||
for (const auto &highlightedUser : userItems)
|
||||
{
|
||||
if (highlightedUser.isMatch(username))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
HighlightBlacklistModel *HighlightController::createBlacklistModel(
|
||||
QObject *parent)
|
||||
{
|
||||
auto *model = new HighlightBlacklistModel(parent);
|
||||
model->init(&this->blacklistedUsers);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
bool HighlightController::blacklistContains(const QString &username)
|
||||
{
|
||||
for (const auto &blacklistedUser : this->blacklistedUsers)
|
||||
{
|
||||
if (blacklistedUser.isMatch(username))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HighlightController::addHighlight(const MessagePtr &msg)
|
||||
{
|
||||
// static NotificationPopup popup;
|
||||
|
||||
// popup.updatePosition();
|
||||
// popup.addMessage(msg);
|
||||
// popup.show();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/ChatterinoSetting.hpp"
|
||||
#include "common/SignalVector.hpp"
|
||||
#include "common/Singleton.hpp"
|
||||
#include "controllers/highlights/HighlightBlacklistUser.hpp"
|
||||
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
|
||||
class Settings;
|
||||
class Paths;
|
||||
|
||||
class UserHighlightModel;
|
||||
class HighlightModel;
|
||||
class HighlightBlacklistModel;
|
||||
|
||||
class HighlightController final : public Singleton
|
||||
{
|
||||
public:
|
||||
HighlightController();
|
||||
|
||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
||||
|
||||
UnsortedSignalVector<HighlightPhrase> phrases;
|
||||
UnsortedSignalVector<HighlightBlacklistUser> blacklistedUsers;
|
||||
UnsortedSignalVector<HighlightPhrase> highlightedUsers;
|
||||
|
||||
HighlightModel *createModel(QObject *parent);
|
||||
HighlightBlacklistModel *createBlacklistModel(QObject *parent);
|
||||
UserHighlightModel *createUserModel(QObject *parent);
|
||||
|
||||
bool isHighlightedUser(const QString &username);
|
||||
bool blacklistContains(const QString &username);
|
||||
|
||||
void addHighlight(const MessagePtr &msg);
|
||||
|
||||
private:
|
||||
bool initialized_ = false;
|
||||
|
||||
ChatterinoSetting<std::vector<HighlightPhrase>> highlightsSetting_ = {
|
||||
"/highlighting/highlights"};
|
||||
ChatterinoSetting<std::vector<HighlightBlacklistUser>> blacklistSetting_ = {
|
||||
"/highlighting/blacklist"};
|
||||
ChatterinoSetting<std::vector<HighlightPhrase>> userSetting_ = {
|
||||
"/highlighting/users"};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "common/ChatterinoSetting.hpp"
|
||||
#include "common/SignalVector.hpp"
|
||||
#include "common/Singleton.hpp"
|
||||
#include "controllers/highlights/HighlightBlacklistUser.hpp"
|
||||
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
|
||||
class Settings;
|
||||
class Paths;
|
||||
|
||||
class UserHighlightModel;
|
||||
class HighlightModel;
|
||||
class HighlightBlacklistModel;
|
||||
|
||||
class HighlightController final : public Singleton
|
||||
{
|
||||
public:
|
||||
HighlightController();
|
||||
|
||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
||||
|
||||
UnsortedSignalVector<HighlightPhrase> phrases;
|
||||
UnsortedSignalVector<HighlightBlacklistUser> blacklistedUsers;
|
||||
UnsortedSignalVector<HighlightPhrase> highlightedUsers;
|
||||
|
||||
HighlightModel *createModel(QObject *parent);
|
||||
HighlightBlacklistModel *createBlacklistModel(QObject *parent);
|
||||
UserHighlightModel *createUserModel(QObject *parent);
|
||||
|
||||
bool isHighlightedUser(const QString &username);
|
||||
bool blacklistContains(const QString &username);
|
||||
|
||||
void addHighlight(const MessagePtr &msg);
|
||||
|
||||
private:
|
||||
bool initialized_ = false;
|
||||
|
||||
ChatterinoSetting<std::vector<HighlightPhrase>> highlightsSetting_ = {
|
||||
"/highlighting/highlights"};
|
||||
ChatterinoSetting<std::vector<HighlightBlacklistUser>> blacklistSetting_ = {
|
||||
"/highlighting/blacklist"};
|
||||
ChatterinoSetting<std::vector<HighlightPhrase>> userSetting_ = {
|
||||
"/highlighting/users"};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,131 +1,131 @@
|
|||
#include "HighlightModel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// commandmodel
|
||||
HighlightModel::HighlightModel(QObject *parent)
|
||||
: SignalVectorModel<HighlightPhrase>(5, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
HighlightPhrase HighlightModel::getItemFromRow(
|
||||
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
|
||||
{
|
||||
// key, alert, sound, regex, case-sensitivity
|
||||
|
||||
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
|
||||
row[1]->data(Qt::CheckStateRole).toBool(),
|
||||
row[2]->data(Qt::CheckStateRole).toBool(),
|
||||
row[3]->data(Qt::CheckStateRole).toBool(),
|
||||
row[4]->data(Qt::CheckStateRole).toBool()};
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void HighlightModel::getRowFromItem(const HighlightPhrase &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item.getPattern());
|
||||
setBoolItem(row[1], item.getAlert());
|
||||
setBoolItem(row[2], item.getSound());
|
||||
setBoolItem(row[3], item.isRegex());
|
||||
setBoolItem(row[4], item.isCaseSensitive());
|
||||
}
|
||||
|
||||
void HighlightModel::afterInit()
|
||||
{
|
||||
std::vector<QStandardItem *> usernameRow = this->createRow();
|
||||
setBoolItem(usernameRow[0], getSettings()->enableSelfHighlight.getValue(),
|
||||
true, false);
|
||||
usernameRow[0]->setData("Your username (automatic)", Qt::DisplayRole);
|
||||
setBoolItem(usernameRow[1],
|
||||
getSettings()->enableSelfHighlightTaskbar.getValue(), true,
|
||||
false);
|
||||
setBoolItem(usernameRow[2],
|
||||
getSettings()->enableSelfHighlightSound.getValue(), true,
|
||||
false);
|
||||
usernameRow[3]->setFlags(0);
|
||||
this->insertCustomRow(usernameRow, 0);
|
||||
std::vector<QStandardItem *> whisperRow = this->createRow();
|
||||
setBoolItem(whisperRow[0], getSettings()->enableWhisperHighlight.getValue(),
|
||||
true, false);
|
||||
whisperRow[0]->setData("Whispers", Qt::DisplayRole);
|
||||
setBoolItem(whisperRow[1],
|
||||
getSettings()->enableWhisperHighlightTaskbar.getValue(), true,
|
||||
false);
|
||||
setBoolItem(whisperRow[2],
|
||||
getSettings()->enableWhisperHighlightSound.getValue(), true,
|
||||
false);
|
||||
whisperRow[3]->setFlags(0);
|
||||
this->insertCustomRow(whisperRow, 1);
|
||||
}
|
||||
|
||||
void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
||||
int column, const QVariant &value,
|
||||
int role, int rowIndex)
|
||||
{
|
||||
switch (column)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
{
|
||||
getSettings()->enableSelfHighlight.setValue(value.toBool());
|
||||
}
|
||||
else if (rowIndex == 1)
|
||||
{
|
||||
getSettings()->enableWhisperHighlight.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
{
|
||||
getSettings()->enableSelfHighlightTaskbar.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
else if (rowIndex == 1)
|
||||
{
|
||||
getSettings()->enableWhisperHighlightTaskbar.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
{
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
{
|
||||
getSettings()->enableSelfHighlightSound.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
else if (rowIndex == 1)
|
||||
{
|
||||
getSettings()->enableWhisperHighlightSound.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
// empty element
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "HighlightModel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// commandmodel
|
||||
HighlightModel::HighlightModel(QObject *parent)
|
||||
: SignalVectorModel<HighlightPhrase>(5, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
HighlightPhrase HighlightModel::getItemFromRow(
|
||||
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
|
||||
{
|
||||
// key, alert, sound, regex, case-sensitivity
|
||||
|
||||
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
|
||||
row[1]->data(Qt::CheckStateRole).toBool(),
|
||||
row[2]->data(Qt::CheckStateRole).toBool(),
|
||||
row[3]->data(Qt::CheckStateRole).toBool(),
|
||||
row[4]->data(Qt::CheckStateRole).toBool()};
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void HighlightModel::getRowFromItem(const HighlightPhrase &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item.getPattern());
|
||||
setBoolItem(row[1], item.getAlert());
|
||||
setBoolItem(row[2], item.getSound());
|
||||
setBoolItem(row[3], item.isRegex());
|
||||
setBoolItem(row[4], item.isCaseSensitive());
|
||||
}
|
||||
|
||||
void HighlightModel::afterInit()
|
||||
{
|
||||
std::vector<QStandardItem *> usernameRow = this->createRow();
|
||||
setBoolItem(usernameRow[0], getSettings()->enableSelfHighlight.getValue(),
|
||||
true, false);
|
||||
usernameRow[0]->setData("Your username (automatic)", Qt::DisplayRole);
|
||||
setBoolItem(usernameRow[1],
|
||||
getSettings()->enableSelfHighlightTaskbar.getValue(), true,
|
||||
false);
|
||||
setBoolItem(usernameRow[2],
|
||||
getSettings()->enableSelfHighlightSound.getValue(), true,
|
||||
false);
|
||||
usernameRow[3]->setFlags(0);
|
||||
this->insertCustomRow(usernameRow, 0);
|
||||
std::vector<QStandardItem *> whisperRow = this->createRow();
|
||||
setBoolItem(whisperRow[0], getSettings()->enableWhisperHighlight.getValue(),
|
||||
true, false);
|
||||
whisperRow[0]->setData("Whispers", Qt::DisplayRole);
|
||||
setBoolItem(whisperRow[1],
|
||||
getSettings()->enableWhisperHighlightTaskbar.getValue(), true,
|
||||
false);
|
||||
setBoolItem(whisperRow[2],
|
||||
getSettings()->enableWhisperHighlightSound.getValue(), true,
|
||||
false);
|
||||
whisperRow[3]->setFlags(0);
|
||||
this->insertCustomRow(whisperRow, 1);
|
||||
}
|
||||
|
||||
void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
||||
int column, const QVariant &value,
|
||||
int role, int rowIndex)
|
||||
{
|
||||
switch (column)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
{
|
||||
getSettings()->enableSelfHighlight.setValue(value.toBool());
|
||||
}
|
||||
else if (rowIndex == 1)
|
||||
{
|
||||
getSettings()->enableWhisperHighlight.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
{
|
||||
getSettings()->enableSelfHighlightTaskbar.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
else if (rowIndex == 1)
|
||||
{
|
||||
getSettings()->enableWhisperHighlightTaskbar.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
{
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
{
|
||||
getSettings()->enableSelfHighlightSound.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
else if (rowIndex == 1)
|
||||
{
|
||||
getSettings()->enableWhisperHighlightSound.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
// empty element
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class HighlightController;
|
||||
|
||||
class HighlightModel : public SignalVectorModel<HighlightPhrase>
|
||||
{
|
||||
explicit HighlightModel(QObject *parent);
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual HighlightPhrase getItemFromRow(
|
||||
std::vector<QStandardItem *> &row,
|
||||
const HighlightPhrase &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const HighlightPhrase &item,
|
||||
std::vector<QStandardItem *> &row) override;
|
||||
|
||||
virtual void afterInit() override;
|
||||
|
||||
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
||||
int column, const QVariant &value, int role,
|
||||
int rowIndex) override;
|
||||
|
||||
friend class HighlightController;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class HighlightController;
|
||||
|
||||
class HighlightModel : public SignalVectorModel<HighlightPhrase>
|
||||
{
|
||||
explicit HighlightModel(QObject *parent);
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual HighlightPhrase getItemFromRow(
|
||||
std::vector<QStandardItem *> &row,
|
||||
const HighlightPhrase &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const HighlightPhrase &item,
|
||||
std::vector<QStandardItem *> &row) override;
|
||||
|
||||
virtual void afterInit() override;
|
||||
|
||||
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
||||
int column, const QVariant &value, int role,
|
||||
int rowIndex) override;
|
||||
|
||||
friend class HighlightController;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,127 +1,127 @@
|
|||
#pragma once
|
||||
|
||||
#include "util/RapidJsonSerializeQString.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <pajlada/serialize.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class HighlightPhrase
|
||||
{
|
||||
public:
|
||||
bool operator==(const HighlightPhrase &other) const
|
||||
{
|
||||
return std::tie(this->pattern_, this->sound_, this->alert_,
|
||||
this->isRegex_, this->caseSensitive_) ==
|
||||
std::tie(other.pattern_, other.sound_, other.alert_,
|
||||
other.isRegex_, other.caseSensitive_);
|
||||
}
|
||||
|
||||
HighlightPhrase(const QString &pattern, bool alert, bool sound,
|
||||
bool isRegex, bool caseSensitive)
|
||||
: pattern_(pattern)
|
||||
, alert_(alert)
|
||||
, sound_(sound)
|
||||
, isRegex_(isRegex)
|
||||
, caseSensitive_(caseSensitive)
|
||||
, regex_(
|
||||
isRegex_ ? pattern
|
||||
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
|
||||
QRegularExpression::UseUnicodePropertiesOption |
|
||||
(caseSensitive_ ? QRegularExpression::NoPatternOption
|
||||
: QRegularExpression::CaseInsensitiveOption))
|
||||
{
|
||||
}
|
||||
|
||||
const QString &getPattern() const
|
||||
{
|
||||
return this->pattern_;
|
||||
}
|
||||
bool getAlert() const
|
||||
{
|
||||
return this->alert_;
|
||||
}
|
||||
bool getSound() const
|
||||
{
|
||||
return this->sound_;
|
||||
}
|
||||
bool isRegex() const
|
||||
{
|
||||
return this->isRegex_;
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return !this->pattern_.isEmpty() && this->regex_.isValid();
|
||||
}
|
||||
|
||||
bool isMatch(const QString &subject) const
|
||||
{
|
||||
return this->isValid() && this->regex_.match(subject).hasMatch();
|
||||
}
|
||||
|
||||
bool isCaseSensitive() const
|
||||
{
|
||||
return this->caseSensitive_;
|
||||
}
|
||||
|
||||
private:
|
||||
QString pattern_;
|
||||
bool alert_;
|
||||
bool sound_;
|
||||
bool isRegex_;
|
||||
bool caseSensitive_;
|
||||
QRegularExpression regex_;
|
||||
};
|
||||
} // namespace chatterino
|
||||
|
||||
namespace pajlada {
|
||||
|
||||
template <>
|
||||
struct Serialize<chatterino::HighlightPhrase> {
|
||||
static rapidjson::Value get(const chatterino::HighlightPhrase &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
rapidjson::Value ret(rapidjson::kObjectType);
|
||||
|
||||
chatterino::rj::set(ret, "pattern", value.getPattern(), a);
|
||||
chatterino::rj::set(ret, "alert", value.getAlert(), a);
|
||||
chatterino::rj::set(ret, "sound", value.getSound(), a);
|
||||
chatterino::rj::set(ret, "regex", value.isRegex(), a);
|
||||
chatterino::rj::set(ret, "case", value.isCaseSensitive(), a);
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Deserialize<chatterino::HighlightPhrase> {
|
||||
static chatterino::HighlightPhrase get(const rapidjson::Value &value)
|
||||
{
|
||||
if (!value.IsObject())
|
||||
{
|
||||
return chatterino::HighlightPhrase(QString(), true, false, false,
|
||||
false);
|
||||
}
|
||||
|
||||
QString _pattern;
|
||||
bool _alert = true;
|
||||
bool _sound = false;
|
||||
bool _isRegex = false;
|
||||
bool _caseSensitive = false;
|
||||
|
||||
chatterino::rj::getSafe(value, "pattern", _pattern);
|
||||
chatterino::rj::getSafe(value, "alert", _alert);
|
||||
chatterino::rj::getSafe(value, "sound", _sound);
|
||||
chatterino::rj::getSafe(value, "regex", _isRegex);
|
||||
chatterino::rj::getSafe(value, "case", _caseSensitive);
|
||||
|
||||
return chatterino::HighlightPhrase(_pattern, _alert, _sound,
|
||||
_isRegex, _caseSensitive);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace pajlada
|
||||
#pragma once
|
||||
|
||||
#include "util/RapidJsonSerializeQString.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <pajlada/serialize.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class HighlightPhrase
|
||||
{
|
||||
public:
|
||||
bool operator==(const HighlightPhrase &other) const
|
||||
{
|
||||
return std::tie(this->pattern_, this->sound_, this->alert_,
|
||||
this->isRegex_, this->caseSensitive_) ==
|
||||
std::tie(other.pattern_, other.sound_, other.alert_,
|
||||
other.isRegex_, other.caseSensitive_);
|
||||
}
|
||||
|
||||
HighlightPhrase(const QString &pattern, bool alert, bool sound,
|
||||
bool isRegex, bool caseSensitive)
|
||||
: pattern_(pattern)
|
||||
, alert_(alert)
|
||||
, sound_(sound)
|
||||
, isRegex_(isRegex)
|
||||
, caseSensitive_(caseSensitive)
|
||||
, regex_(
|
||||
isRegex_ ? pattern
|
||||
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
|
||||
QRegularExpression::UseUnicodePropertiesOption |
|
||||
(caseSensitive_ ? QRegularExpression::NoPatternOption
|
||||
: QRegularExpression::CaseInsensitiveOption))
|
||||
{
|
||||
}
|
||||
|
||||
const QString &getPattern() const
|
||||
{
|
||||
return this->pattern_;
|
||||
}
|
||||
bool getAlert() const
|
||||
{
|
||||
return this->alert_;
|
||||
}
|
||||
bool getSound() const
|
||||
{
|
||||
return this->sound_;
|
||||
}
|
||||
bool isRegex() const
|
||||
{
|
||||
return this->isRegex_;
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return !this->pattern_.isEmpty() && this->regex_.isValid();
|
||||
}
|
||||
|
||||
bool isMatch(const QString &subject) const
|
||||
{
|
||||
return this->isValid() && this->regex_.match(subject).hasMatch();
|
||||
}
|
||||
|
||||
bool isCaseSensitive() const
|
||||
{
|
||||
return this->caseSensitive_;
|
||||
}
|
||||
|
||||
private:
|
||||
QString pattern_;
|
||||
bool alert_;
|
||||
bool sound_;
|
||||
bool isRegex_;
|
||||
bool caseSensitive_;
|
||||
QRegularExpression regex_;
|
||||
};
|
||||
} // namespace chatterino
|
||||
|
||||
namespace pajlada {
|
||||
|
||||
template <>
|
||||
struct Serialize<chatterino::HighlightPhrase> {
|
||||
static rapidjson::Value get(const chatterino::HighlightPhrase &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
rapidjson::Value ret(rapidjson::kObjectType);
|
||||
|
||||
chatterino::rj::set(ret, "pattern", value.getPattern(), a);
|
||||
chatterino::rj::set(ret, "alert", value.getAlert(), a);
|
||||
chatterino::rj::set(ret, "sound", value.getSound(), a);
|
||||
chatterino::rj::set(ret, "regex", value.isRegex(), a);
|
||||
chatterino::rj::set(ret, "case", value.isCaseSensitive(), a);
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Deserialize<chatterino::HighlightPhrase> {
|
||||
static chatterino::HighlightPhrase get(const rapidjson::Value &value)
|
||||
{
|
||||
if (!value.IsObject())
|
||||
{
|
||||
return chatterino::HighlightPhrase(QString(), true, false, false,
|
||||
false);
|
||||
}
|
||||
|
||||
QString _pattern;
|
||||
bool _alert = true;
|
||||
bool _sound = false;
|
||||
bool _isRegex = false;
|
||||
bool _caseSensitive = false;
|
||||
|
||||
chatterino::rj::getSafe(value, "pattern", _pattern);
|
||||
chatterino::rj::getSafe(value, "alert", _alert);
|
||||
chatterino::rj::getSafe(value, "sound", _sound);
|
||||
chatterino::rj::getSafe(value, "regex", _isRegex);
|
||||
chatterino::rj::getSafe(value, "case", _caseSensitive);
|
||||
|
||||
return chatterino::HighlightPhrase(_pattern, _alert, _sound,
|
||||
_isRegex, _caseSensitive);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace pajlada
|
||||
|
|
|
@ -1,121 +1,121 @@
|
|||
#include "ModerationAction.hpp"
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include "Application.hpp"
|
||||
#include "messages/Image.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// ModerationAction::ModerationAction(Image *_image, const QString &_action)
|
||||
// : _isImage(true)
|
||||
// , image(_image)
|
||||
// , action(_action)
|
||||
//{
|
||||
//}
|
||||
|
||||
// ModerationAction::ModerationAction(const QString &_line1, const QString
|
||||
// &_line2,
|
||||
// const QString &_action)
|
||||
// : _isImage(false)
|
||||
// , image(nullptr)
|
||||
// , line1(_line1)
|
||||
// , line2(_line2)
|
||||
// , action(_action)
|
||||
//{
|
||||
//}
|
||||
|
||||
ModerationAction::ModerationAction(const QString &action)
|
||||
: action_(action)
|
||||
{
|
||||
static QRegularExpression replaceRegex("[!/.]");
|
||||
static QRegularExpression timeoutRegex("^[./]timeout.* (\\d+)");
|
||||
|
||||
auto timeoutMatch = timeoutRegex.match(action);
|
||||
|
||||
if (timeoutMatch.hasMatch())
|
||||
{
|
||||
// if (multipleTimeouts > 1) {
|
||||
// QString line1;
|
||||
// QString line2;
|
||||
|
||||
int amount = timeoutMatch.captured(1).toInt();
|
||||
|
||||
if (amount < 60)
|
||||
{
|
||||
this->line1_ = QString::number(amount);
|
||||
this->line2_ = "s";
|
||||
}
|
||||
else if (amount < 60 * 60)
|
||||
{
|
||||
this->line1_ = QString::number(amount / 60);
|
||||
this->line2_ = "m";
|
||||
}
|
||||
else if (amount < 60 * 60 * 24)
|
||||
{
|
||||
this->line1_ = QString::number(amount / 60 / 60);
|
||||
this->line2_ = "h";
|
||||
}
|
||||
else
|
||||
{
|
||||
this->line1_ = QString::number(amount / 60 / 60 / 24);
|
||||
this->line2_ = "d";
|
||||
}
|
||||
|
||||
// line1 = this->line1_;
|
||||
// line2 = this->line2_;
|
||||
// } else {
|
||||
// this->_moderationActions.emplace_back(app->resources->buttonTimeout,
|
||||
// str);
|
||||
// }
|
||||
}
|
||||
else if (action.startsWith("/ban "))
|
||||
{
|
||||
this->image_ = Image::fromPixmap(getApp()->resources->buttons.ban);
|
||||
}
|
||||
else if (action.startsWith("/delete "))
|
||||
{
|
||||
this->image_ = Image::fromPixmap(getApp()->resources->buttons.trashCan);
|
||||
}
|
||||
else
|
||||
{
|
||||
QString xD = action;
|
||||
|
||||
xD.replace(replaceRegex, "");
|
||||
|
||||
this->line1_ = xD.mid(0, 2);
|
||||
this->line2_ = xD.mid(2, 2);
|
||||
}
|
||||
}
|
||||
|
||||
bool ModerationAction::operator==(const ModerationAction &other) const
|
||||
{
|
||||
return this == std::addressof(other);
|
||||
}
|
||||
|
||||
bool ModerationAction::isImage() const
|
||||
{
|
||||
return bool(this->image_);
|
||||
}
|
||||
|
||||
const boost::optional<ImagePtr> &ModerationAction::getImage() const
|
||||
{
|
||||
return this->image_;
|
||||
}
|
||||
|
||||
const QString &ModerationAction::getLine1() const
|
||||
{
|
||||
return this->line1_;
|
||||
}
|
||||
|
||||
const QString &ModerationAction::getLine2() const
|
||||
{
|
||||
return this->line2_;
|
||||
}
|
||||
|
||||
const QString &ModerationAction::getAction() const
|
||||
{
|
||||
return this->action_;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "ModerationAction.hpp"
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include "Application.hpp"
|
||||
#include "messages/Image.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// ModerationAction::ModerationAction(Image *_image, const QString &_action)
|
||||
// : _isImage(true)
|
||||
// , image(_image)
|
||||
// , action(_action)
|
||||
//{
|
||||
//}
|
||||
|
||||
// ModerationAction::ModerationAction(const QString &_line1, const QString
|
||||
// &_line2,
|
||||
// const QString &_action)
|
||||
// : _isImage(false)
|
||||
// , image(nullptr)
|
||||
// , line1(_line1)
|
||||
// , line2(_line2)
|
||||
// , action(_action)
|
||||
//{
|
||||
//}
|
||||
|
||||
ModerationAction::ModerationAction(const QString &action)
|
||||
: action_(action)
|
||||
{
|
||||
static QRegularExpression replaceRegex("[!/.]");
|
||||
static QRegularExpression timeoutRegex("^[./]timeout.* (\\d+)");
|
||||
|
||||
auto timeoutMatch = timeoutRegex.match(action);
|
||||
|
||||
if (timeoutMatch.hasMatch())
|
||||
{
|
||||
// if (multipleTimeouts > 1) {
|
||||
// QString line1;
|
||||
// QString line2;
|
||||
|
||||
int amount = timeoutMatch.captured(1).toInt();
|
||||
|
||||
if (amount < 60)
|
||||
{
|
||||
this->line1_ = QString::number(amount);
|
||||
this->line2_ = "s";
|
||||
}
|
||||
else if (amount < 60 * 60)
|
||||
{
|
||||
this->line1_ = QString::number(amount / 60);
|
||||
this->line2_ = "m";
|
||||
}
|
||||
else if (amount < 60 * 60 * 24)
|
||||
{
|
||||
this->line1_ = QString::number(amount / 60 / 60);
|
||||
this->line2_ = "h";
|
||||
}
|
||||
else
|
||||
{
|
||||
this->line1_ = QString::number(amount / 60 / 60 / 24);
|
||||
this->line2_ = "d";
|
||||
}
|
||||
|
||||
// line1 = this->line1_;
|
||||
// line2 = this->line2_;
|
||||
// } else {
|
||||
// this->_moderationActions.emplace_back(app->resources->buttonTimeout,
|
||||
// str);
|
||||
// }
|
||||
}
|
||||
else if (action.startsWith("/ban "))
|
||||
{
|
||||
this->image_ = Image::fromPixmap(getApp()->resources->buttons.ban);
|
||||
}
|
||||
else if (action.startsWith("/delete "))
|
||||
{
|
||||
this->image_ = Image::fromPixmap(getApp()->resources->buttons.trashCan);
|
||||
}
|
||||
else
|
||||
{
|
||||
QString xD = action;
|
||||
|
||||
xD.replace(replaceRegex, "");
|
||||
|
||||
this->line1_ = xD.mid(0, 2);
|
||||
this->line2_ = xD.mid(2, 2);
|
||||
}
|
||||
}
|
||||
|
||||
bool ModerationAction::operator==(const ModerationAction &other) const
|
||||
{
|
||||
return this == std::addressof(other);
|
||||
}
|
||||
|
||||
bool ModerationAction::isImage() const
|
||||
{
|
||||
return bool(this->image_);
|
||||
}
|
||||
|
||||
const boost::optional<ImagePtr> &ModerationAction::getImage() const
|
||||
{
|
||||
return this->image_;
|
||||
}
|
||||
|
||||
const QString &ModerationAction::getLine1() const
|
||||
{
|
||||
return this->line1_;
|
||||
}
|
||||
|
||||
const QString &ModerationAction::getLine2() const
|
||||
{
|
||||
return this->line2_;
|
||||
}
|
||||
|
||||
const QString &ModerationAction::getAction() const
|
||||
{
|
||||
return this->action_;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,68 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <boost/optional.hpp>
|
||||
#include <pajlada/serialize.hpp>
|
||||
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Image;
|
||||
using ImagePtr = std::shared_ptr<Image>;
|
||||
|
||||
class ModerationAction
|
||||
{
|
||||
public:
|
||||
ModerationAction(const QString &action);
|
||||
|
||||
bool operator==(const ModerationAction &other) const;
|
||||
|
||||
bool isImage() const;
|
||||
const boost::optional<ImagePtr> &getImage() const;
|
||||
const QString &getLine1() const;
|
||||
const QString &getLine2() const;
|
||||
const QString &getAction() const;
|
||||
|
||||
private:
|
||||
boost::optional<ImagePtr> image_;
|
||||
QString line1_;
|
||||
QString line2_;
|
||||
QString action_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace pajlada {
|
||||
|
||||
template <>
|
||||
struct Serialize<chatterino::ModerationAction> {
|
||||
static rapidjson::Value get(const chatterino::ModerationAction &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
rapidjson::Value ret(rapidjson::kObjectType);
|
||||
|
||||
chatterino::rj::set(ret, "pattern", value.getAction(), a);
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Deserialize<chatterino::ModerationAction> {
|
||||
static chatterino::ModerationAction get(const rapidjson::Value &value)
|
||||
{
|
||||
if (!value.IsObject())
|
||||
{
|
||||
return chatterino::ModerationAction(QString());
|
||||
}
|
||||
|
||||
QString pattern;
|
||||
|
||||
chatterino::rj::getSafe(value, "pattern", pattern);
|
||||
|
||||
return chatterino::ModerationAction(pattern);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace pajlada
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <boost/optional.hpp>
|
||||
#include <pajlada/serialize.hpp>
|
||||
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Image;
|
||||
using ImagePtr = std::shared_ptr<Image>;
|
||||
|
||||
class ModerationAction
|
||||
{
|
||||
public:
|
||||
ModerationAction(const QString &action);
|
||||
|
||||
bool operator==(const ModerationAction &other) const;
|
||||
|
||||
bool isImage() const;
|
||||
const boost::optional<ImagePtr> &getImage() const;
|
||||
const QString &getLine1() const;
|
||||
const QString &getLine2() const;
|
||||
const QString &getAction() const;
|
||||
|
||||
private:
|
||||
boost::optional<ImagePtr> image_;
|
||||
QString line1_;
|
||||
QString line2_;
|
||||
QString action_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace pajlada {
|
||||
|
||||
template <>
|
||||
struct Serialize<chatterino::ModerationAction> {
|
||||
static rapidjson::Value get(const chatterino::ModerationAction &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
rapidjson::Value ret(rapidjson::kObjectType);
|
||||
|
||||
chatterino::rj::set(ret, "pattern", value.getAction(), a);
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Deserialize<chatterino::ModerationAction> {
|
||||
static chatterino::ModerationAction get(const rapidjson::Value &value)
|
||||
{
|
||||
if (!value.IsObject())
|
||||
{
|
||||
return chatterino::ModerationAction(QString());
|
||||
}
|
||||
|
||||
QString pattern;
|
||||
|
||||
chatterino::rj::getSafe(value, "pattern", pattern);
|
||||
|
||||
return chatterino::ModerationAction(pattern);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace pajlada
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
#include "ModerationActionModel.hpp"
|
||||
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// commandmodel
|
||||
ModerationActionModel ::ModerationActionModel(QObject *parent)
|
||||
: SignalVectorModel<ModerationAction>(1, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
ModerationAction ModerationActionModel::getItemFromRow(
|
||||
std::vector<QStandardItem *> &row, const ModerationAction &original)
|
||||
{
|
||||
return ModerationAction(row[0]->data(Qt::DisplayRole).toString());
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void ModerationActionModel::getRowFromItem(const ModerationAction &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item.getAction());
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "ModerationActionModel.hpp"
|
||||
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// commandmodel
|
||||
ModerationActionModel ::ModerationActionModel(QObject *parent)
|
||||
: SignalVectorModel<ModerationAction>(1, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
ModerationAction ModerationActionModel::getItemFromRow(
|
||||
std::vector<QStandardItem *> &row, const ModerationAction &original)
|
||||
{
|
||||
return ModerationAction(row[0]->data(Qt::DisplayRole).toString());
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void ModerationActionModel::getRowFromItem(const ModerationAction &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item.getAction());
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "controllers/moderationactions/ModerationAction.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class ModerationActions;
|
||||
|
||||
class ModerationActionModel : public SignalVectorModel<ModerationAction>
|
||||
{
|
||||
public:
|
||||
explicit ModerationActionModel(QObject *parent);
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual ModerationAction getItemFromRow(
|
||||
std::vector<QStandardItem *> &row,
|
||||
const ModerationAction &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const ModerationAction &item,
|
||||
std::vector<QStandardItem *> &row) override;
|
||||
|
||||
friend class HighlightController;
|
||||
|
||||
friend class ModerationActions;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "controllers/moderationactions/ModerationAction.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class ModerationActions;
|
||||
|
||||
class ModerationActionModel : public SignalVectorModel<ModerationAction>
|
||||
{
|
||||
public:
|
||||
explicit ModerationActionModel(QObject *parent);
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual ModerationAction getItemFromRow(
|
||||
std::vector<QStandardItem *> &row,
|
||||
const ModerationAction &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const ModerationAction &item,
|
||||
std::vector<QStandardItem *> &row) override;
|
||||
|
||||
friend class HighlightController;
|
||||
|
||||
friend class ModerationActions;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,42 +1,42 @@
|
|||
#include "ModerationActions.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/moderationactions/ModerationActionModel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
ModerationActions::ModerationActions()
|
||||
{
|
||||
}
|
||||
|
||||
void ModerationActions::initialize(Settings &settings, Paths &paths)
|
||||
{
|
||||
assert(!this->initialized_);
|
||||
this->initialized_ = true;
|
||||
|
||||
this->setting_ =
|
||||
std::make_unique<ChatterinoSetting<std::vector<ModerationAction>>>(
|
||||
"/moderation/actions");
|
||||
|
||||
for (auto &val : this->setting_->getValue())
|
||||
{
|
||||
this->items.insertItem(val);
|
||||
}
|
||||
|
||||
this->items.delayedItemsChanged.connect([this] { //
|
||||
this->setting_->setValue(this->items.getVector());
|
||||
});
|
||||
}
|
||||
|
||||
ModerationActionModel *ModerationActions::createModel(QObject *parent)
|
||||
{
|
||||
ModerationActionModel *model = new ModerationActionModel(parent);
|
||||
model->init(&this->items);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "ModerationActions.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/moderationactions/ModerationActionModel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
ModerationActions::ModerationActions()
|
||||
{
|
||||
}
|
||||
|
||||
void ModerationActions::initialize(Settings &settings, Paths &paths)
|
||||
{
|
||||
assert(!this->initialized_);
|
||||
this->initialized_ = true;
|
||||
|
||||
this->setting_ =
|
||||
std::make_unique<ChatterinoSetting<std::vector<ModerationAction>>>(
|
||||
"/moderation/actions");
|
||||
|
||||
for (auto &val : this->setting_->getValue())
|
||||
{
|
||||
this->items.insertItem(val);
|
||||
}
|
||||
|
||||
this->items.delayedItemsChanged.connect([this] { //
|
||||
this->setting_->setValue(this->items.getVector());
|
||||
});
|
||||
}
|
||||
|
||||
ModerationActionModel *ModerationActions::createModel(QObject *parent)
|
||||
{
|
||||
ModerationActionModel *model = new ModerationActionModel(parent);
|
||||
model->init(&this->items);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/Singleton.hpp"
|
||||
|
||||
#include "common/ChatterinoSetting.hpp"
|
||||
#include "common/SignalVector.hpp"
|
||||
#include "controllers/moderationactions/ModerationAction.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Settings;
|
||||
class Paths;
|
||||
|
||||
class ModerationActionModel;
|
||||
|
||||
class ModerationActions final : public Singleton
|
||||
{
|
||||
public:
|
||||
ModerationActions();
|
||||
|
||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
||||
|
||||
UnsortedSignalVector<ModerationAction> items;
|
||||
|
||||
ModerationActionModel *createModel(QObject *parent);
|
||||
|
||||
private:
|
||||
std::unique_ptr<ChatterinoSetting<std::vector<ModerationAction>>> setting_;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "common/Singleton.hpp"
|
||||
|
||||
#include "common/ChatterinoSetting.hpp"
|
||||
#include "common/SignalVector.hpp"
|
||||
#include "controllers/moderationactions/ModerationAction.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Settings;
|
||||
class Paths;
|
||||
|
||||
class ModerationActionModel;
|
||||
|
||||
class ModerationActions final : public Singleton
|
||||
{
|
||||
public:
|
||||
ModerationActions();
|
||||
|
||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
||||
|
||||
UnsortedSignalVector<ModerationAction> items;
|
||||
|
||||
ModerationActionModel *createModel(QObject *parent);
|
||||
|
||||
private:
|
||||
std::unique_ptr<ChatterinoSetting<std::vector<ModerationAction>>> setting_;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
#include "TaggedUser.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
TaggedUser::TaggedUser(ProviderId provider, const QString &name,
|
||||
const QString &id)
|
||||
: providerId_(provider)
|
||||
, name_(name)
|
||||
, id_(id)
|
||||
{
|
||||
}
|
||||
|
||||
bool TaggedUser::operator<(const TaggedUser &other) const
|
||||
{
|
||||
return std::tie(this->providerId_, this->name_, this->id_) <
|
||||
std::tie(other.providerId_, other.name_, other.id_);
|
||||
}
|
||||
|
||||
ProviderId TaggedUser::getProviderId() const
|
||||
{
|
||||
return this->providerId_;
|
||||
}
|
||||
|
||||
QString TaggedUser::getName() const
|
||||
{
|
||||
return this->name_;
|
||||
}
|
||||
|
||||
QString TaggedUser::getId() const
|
||||
{
|
||||
return this->id_;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "TaggedUser.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
TaggedUser::TaggedUser(ProviderId provider, const QString &name,
|
||||
const QString &id)
|
||||
: providerId_(provider)
|
||||
, name_(name)
|
||||
, id_(id)
|
||||
{
|
||||
}
|
||||
|
||||
bool TaggedUser::operator<(const TaggedUser &other) const
|
||||
{
|
||||
return std::tie(this->providerId_, this->name_, this->id_) <
|
||||
std::tie(other.providerId_, other.name_, other.id_);
|
||||
}
|
||||
|
||||
ProviderId TaggedUser::getProviderId() const
|
||||
{
|
||||
return this->providerId_;
|
||||
}
|
||||
|
||||
QString TaggedUser::getName() const
|
||||
{
|
||||
return this->name_;
|
||||
}
|
||||
|
||||
QString TaggedUser::getId() const
|
||||
{
|
||||
return this->id_;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/ProviderId.hpp"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class TaggedUser
|
||||
{
|
||||
public:
|
||||
TaggedUser(ProviderId providerId, const QString &name, const QString &id);
|
||||
|
||||
bool operator<(const TaggedUser &other) const;
|
||||
|
||||
ProviderId getProviderId() const;
|
||||
QString getName() const;
|
||||
QString getId() const;
|
||||
|
||||
private:
|
||||
ProviderId providerId_;
|
||||
QString name_;
|
||||
QString id_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "common/ProviderId.hpp"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class TaggedUser
|
||||
{
|
||||
public:
|
||||
TaggedUser(ProviderId providerId, const QString &name, const QString &id);
|
||||
|
||||
bool operator<(const TaggedUser &other) const;
|
||||
|
||||
ProviderId getProviderId() const;
|
||||
QString getName() const;
|
||||
QString getId() const;
|
||||
|
||||
private:
|
||||
ProviderId providerId_;
|
||||
QString name_;
|
||||
QString id_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
#include "TaggedUsersController.hpp"
|
||||
|
||||
#include "controllers/taggedusers/TaggedUsersModel.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
TaggedUsersController::TaggedUsersController()
|
||||
{
|
||||
}
|
||||
|
||||
TaggedUsersModel *TaggedUsersController::createModel(QObject *parent)
|
||||
{
|
||||
TaggedUsersModel *model = new TaggedUsersModel(parent);
|
||||
model->init(&this->users);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "TaggedUsersController.hpp"
|
||||
|
||||
#include "controllers/taggedusers/TaggedUsersModel.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
TaggedUsersController::TaggedUsersController()
|
||||
{
|
||||
}
|
||||
|
||||
TaggedUsersModel *TaggedUsersController::createModel(QObject *parent)
|
||||
{
|
||||
TaggedUsersModel *model = new TaggedUsersModel(parent);
|
||||
model->init(&this->users);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/Singleton.hpp"
|
||||
|
||||
#include "common/SignalVector.hpp"
|
||||
#include "controllers/taggedusers/TaggedUser.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class TaggedUsersModel;
|
||||
|
||||
class TaggedUsersController final : public Singleton
|
||||
{
|
||||
public:
|
||||
TaggedUsersController();
|
||||
|
||||
SortedSignalVector<TaggedUser, std::less<TaggedUser>> users;
|
||||
|
||||
TaggedUsersModel *createModel(QObject *parent = nullptr);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "common/Singleton.hpp"
|
||||
|
||||
#include "common/SignalVector.hpp"
|
||||
#include "controllers/taggedusers/TaggedUser.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class TaggedUsersModel;
|
||||
|
||||
class TaggedUsersController final : public Singleton
|
||||
{
|
||||
public:
|
||||
TaggedUsersController();
|
||||
|
||||
SortedSignalVector<TaggedUser, std::less<TaggedUser>> users;
|
||||
|
||||
TaggedUsersModel *createModel(QObject *parent = nullptr);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,67 +1,67 @@
|
|||
#include "TaggedUsersModel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// commandmodel
|
||||
TaggedUsersModel::TaggedUsersModel(QObject *parent)
|
||||
: SignalVectorModel<TaggedUser>(1, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
TaggedUser TaggedUsersModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const TaggedUser &original)
|
||||
{
|
||||
return original;
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void TaggedUsersModel::getRowFromItem(const TaggedUser &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item.getName());
|
||||
}
|
||||
|
||||
void TaggedUsersModel::afterInit()
|
||||
{
|
||||
// std::vector<QStandardItem *> row = this->createRow();
|
||||
// setBoolItem(row[0],
|
||||
// getSettings()->enableHighlightsSelf.getValue(), true, false);
|
||||
// row[0]->setData("Your username (automatic)", Qt::DisplayRole);
|
||||
// setBoolItem(row[1],
|
||||
// getSettings()->enableHighlightTaskbar.getValue(), true, false);
|
||||
// setBoolItem(row[2],
|
||||
// getSettings()->enableHighlightSound.getValue(), true, false);
|
||||
// row[3]->setFlags(0); this->insertCustomRow(row, 0);
|
||||
}
|
||||
|
||||
// void TaggedUserModel::customRowSetData(const std::vector<QStandardItem *>
|
||||
// &row, int column,
|
||||
// const QVariant &value, int role)
|
||||
//{
|
||||
// switch (column) {
|
||||
// case 0: {
|
||||
// if (role == Qt::CheckStateRole) {
|
||||
// getSettings()->enableHighlightsSelf.setValue(value.toBool());
|
||||
// }
|
||||
// } break;
|
||||
// case 1: {
|
||||
// if (role == Qt::CheckStateRole) {
|
||||
// getSettings()->enableHighlightTaskbar.setValue(value.toBool());
|
||||
// }
|
||||
// } break;
|
||||
// case 2: {
|
||||
// if (role == Qt::CheckStateRole) {
|
||||
// getSettings()->enableHighlightSound.setValue(value.toBool());
|
||||
// }
|
||||
// } break;
|
||||
// case 3: {
|
||||
// // empty element
|
||||
// } break;
|
||||
// }
|
||||
//}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "TaggedUsersModel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// commandmodel
|
||||
TaggedUsersModel::TaggedUsersModel(QObject *parent)
|
||||
: SignalVectorModel<TaggedUser>(1, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
TaggedUser TaggedUsersModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const TaggedUser &original)
|
||||
{
|
||||
return original;
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void TaggedUsersModel::getRowFromItem(const TaggedUser &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item.getName());
|
||||
}
|
||||
|
||||
void TaggedUsersModel::afterInit()
|
||||
{
|
||||
// std::vector<QStandardItem *> row = this->createRow();
|
||||
// setBoolItem(row[0],
|
||||
// getSettings()->enableHighlightsSelf.getValue(), true, false);
|
||||
// row[0]->setData("Your username (automatic)", Qt::DisplayRole);
|
||||
// setBoolItem(row[1],
|
||||
// getSettings()->enableHighlightTaskbar.getValue(), true, false);
|
||||
// setBoolItem(row[2],
|
||||
// getSettings()->enableHighlightSound.getValue(), true, false);
|
||||
// row[3]->setFlags(0); this->insertCustomRow(row, 0);
|
||||
}
|
||||
|
||||
// void TaggedUserModel::customRowSetData(const std::vector<QStandardItem *>
|
||||
// &row, int column,
|
||||
// const QVariant &value, int role)
|
||||
//{
|
||||
// switch (column) {
|
||||
// case 0: {
|
||||
// if (role == Qt::CheckStateRole) {
|
||||
// getSettings()->enableHighlightsSelf.setValue(value.toBool());
|
||||
// }
|
||||
// } break;
|
||||
// case 1: {
|
||||
// if (role == Qt::CheckStateRole) {
|
||||
// getSettings()->enableHighlightTaskbar.setValue(value.toBool());
|
||||
// }
|
||||
// } break;
|
||||
// case 2: {
|
||||
// if (role == Qt::CheckStateRole) {
|
||||
// getSettings()->enableHighlightSound.setValue(value.toBool());
|
||||
// }
|
||||
// } break;
|
||||
// case 3: {
|
||||
// // empty element
|
||||
// } break;
|
||||
// }
|
||||
//}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "controllers/taggedusers/TaggedUser.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class TaggedUsersController;
|
||||
|
||||
class TaggedUsersModel : public SignalVectorModel<TaggedUser>
|
||||
{
|
||||
explicit TaggedUsersModel(QObject *parent);
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual TaggedUser getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const TaggedUser &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const TaggedUser &item,
|
||||
std::vector<QStandardItem *> &row) override;
|
||||
|
||||
virtual void afterInit() override;
|
||||
|
||||
// virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
||||
// int column,
|
||||
// const QVariant &value, int role)
|
||||
// override;
|
||||
|
||||
friend class TaggedUsersController;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "controllers/taggedusers/TaggedUser.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class TaggedUsersController;
|
||||
|
||||
class TaggedUsersModel : public SignalVectorModel<TaggedUser>
|
||||
{
|
||||
explicit TaggedUsersModel(QObject *parent);
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual TaggedUser getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const TaggedUser &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const TaggedUser &item,
|
||||
std::vector<QStandardItem *> &row) override;
|
||||
|
||||
virtual void afterInit() override;
|
||||
|
||||
// virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
||||
// int column,
|
||||
// const QVariant &value, int role)
|
||||
// override;
|
||||
|
||||
friend class TaggedUsersController;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
#include "MessageContainer.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
MessageContainer::MessageContainer()
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "MessageContainer.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
MessageContainer::MessageContainer()
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class MessageContainer
|
||||
{
|
||||
public:
|
||||
MessageContainer();
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class MessageContainer
|
||||
{
|
||||
public:
|
||||
MessageContainer();
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,90 +1,90 @@
|
|||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct SelectionItem {
|
||||
int messageIndex;
|
||||
int charIndex;
|
||||
|
||||
SelectionItem()
|
||||
{
|
||||
this->messageIndex = 0;
|
||||
this->charIndex = 0;
|
||||
}
|
||||
|
||||
SelectionItem(int _messageIndex, int _charIndex)
|
||||
{
|
||||
this->messageIndex = _messageIndex;
|
||||
|
||||
this->charIndex = _charIndex;
|
||||
}
|
||||
|
||||
bool operator<(const SelectionItem &b) const
|
||||
{
|
||||
return std::tie(this->messageIndex, this->charIndex) <
|
||||
std::tie(b.messageIndex, b.charIndex);
|
||||
}
|
||||
|
||||
bool operator>(const SelectionItem &b) const
|
||||
{
|
||||
return !this->operator==(b) && b.operator<(*this);
|
||||
}
|
||||
|
||||
bool operator==(const SelectionItem &b) const
|
||||
{
|
||||
return this->messageIndex == b.messageIndex &&
|
||||
this->charIndex == b.charIndex;
|
||||
}
|
||||
|
||||
bool operator!=(const SelectionItem &b) const
|
||||
{
|
||||
return this->operator==(b);
|
||||
}
|
||||
};
|
||||
|
||||
struct Selection {
|
||||
SelectionItem start;
|
||||
SelectionItem end;
|
||||
SelectionItem selectionMin;
|
||||
SelectionItem selectionMax;
|
||||
|
||||
Selection() = default;
|
||||
|
||||
Selection(const SelectionItem &start, const SelectionItem &end)
|
||||
: start(start)
|
||||
, end(end)
|
||||
, selectionMin(start)
|
||||
, selectionMax(end)
|
||||
{
|
||||
if (selectionMin > selectionMax)
|
||||
{
|
||||
std::swap(this->selectionMin, this->selectionMax);
|
||||
}
|
||||
}
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return this->start == this->end;
|
||||
}
|
||||
|
||||
bool isSingleMessage() const
|
||||
{
|
||||
return this->selectionMin.messageIndex ==
|
||||
this->selectionMax.messageIndex;
|
||||
}
|
||||
};
|
||||
|
||||
struct DoubleClickSelection {
|
||||
int originalStart = 0;
|
||||
int originalEnd = 0;
|
||||
int origMessageIndex;
|
||||
bool selectingLeft = false;
|
||||
bool selectingRight = false;
|
||||
SelectionItem origStartItem;
|
||||
SelectionItem origEndItem;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct SelectionItem {
|
||||
int messageIndex;
|
||||
int charIndex;
|
||||
|
||||
SelectionItem()
|
||||
{
|
||||
this->messageIndex = 0;
|
||||
this->charIndex = 0;
|
||||
}
|
||||
|
||||
SelectionItem(int _messageIndex, int _charIndex)
|
||||
{
|
||||
this->messageIndex = _messageIndex;
|
||||
|
||||
this->charIndex = _charIndex;
|
||||
}
|
||||
|
||||
bool operator<(const SelectionItem &b) const
|
||||
{
|
||||
return std::tie(this->messageIndex, this->charIndex) <
|
||||
std::tie(b.messageIndex, b.charIndex);
|
||||
}
|
||||
|
||||
bool operator>(const SelectionItem &b) const
|
||||
{
|
||||
return !this->operator==(b) && b.operator<(*this);
|
||||
}
|
||||
|
||||
bool operator==(const SelectionItem &b) const
|
||||
{
|
||||
return this->messageIndex == b.messageIndex &&
|
||||
this->charIndex == b.charIndex;
|
||||
}
|
||||
|
||||
bool operator!=(const SelectionItem &b) const
|
||||
{
|
||||
return this->operator==(b);
|
||||
}
|
||||
};
|
||||
|
||||
struct Selection {
|
||||
SelectionItem start;
|
||||
SelectionItem end;
|
||||
SelectionItem selectionMin;
|
||||
SelectionItem selectionMax;
|
||||
|
||||
Selection() = default;
|
||||
|
||||
Selection(const SelectionItem &start, const SelectionItem &end)
|
||||
: start(start)
|
||||
, end(end)
|
||||
, selectionMin(start)
|
||||
, selectionMax(end)
|
||||
{
|
||||
if (selectionMin > selectionMax)
|
||||
{
|
||||
std::swap(this->selectionMin, this->selectionMax);
|
||||
}
|
||||
}
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return this->start == this->end;
|
||||
}
|
||||
|
||||
bool isSingleMessage() const
|
||||
{
|
||||
return this->selectionMin.messageIndex ==
|
||||
this->selectionMax.messageIndex;
|
||||
}
|
||||
};
|
||||
|
||||
struct DoubleClickSelection {
|
||||
int originalStart = 0;
|
||||
int originalEnd = 0;
|
||||
int origMessageIndex;
|
||||
bool selectingLeft = false;
|
||||
bool selectingRight = false;
|
||||
SelectionItem origStartItem;
|
||||
SelectionItem origEndItem;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,117 +1,117 @@
|
|||
#pragma once
|
||||
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/Common.hpp"
|
||||
#include "common/FlagsEnum.hpp"
|
||||
#include "messages/Selection.hpp"
|
||||
#include "messages/layouts/MessageLayoutElement.hpp"
|
||||
|
||||
class QPainter;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum class MessageFlag : uint32_t;
|
||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||
|
||||
struct Margin {
|
||||
int top;
|
||||
int right;
|
||||
int bottom;
|
||||
int left;
|
||||
|
||||
Margin()
|
||||
: Margin(0)
|
||||
{
|
||||
}
|
||||
|
||||
Margin(int value)
|
||||
: Margin(value, value, value, value)
|
||||
{
|
||||
}
|
||||
|
||||
Margin(int _top, int _right, int _bottom, int _left)
|
||||
: top(_top)
|
||||
, right(_right)
|
||||
, bottom(_bottom)
|
||||
, left(_left)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct MessageLayoutContainer {
|
||||
MessageLayoutContainer() = default;
|
||||
|
||||
Margin margin = {4, 8, 4, 8};
|
||||
bool centered = false;
|
||||
bool enableCompactEmotes = false;
|
||||
|
||||
int getHeight() const;
|
||||
int getWidth() const;
|
||||
float getScale() const;
|
||||
|
||||
// methods
|
||||
void begin(int width_, float scale_, MessageFlags flags_);
|
||||
void end();
|
||||
|
||||
void clear();
|
||||
bool canAddElements();
|
||||
void addElement(MessageLayoutElement *element);
|
||||
void addElementNoLineBreak(MessageLayoutElement *element);
|
||||
void breakLine();
|
||||
bool atStartOfLine();
|
||||
bool fitsInLine(int width_);
|
||||
MessageLayoutElement *getElementAt(QPoint point);
|
||||
|
||||
// painting
|
||||
void paintElements(QPainter &painter);
|
||||
void paintAnimatedElements(QPainter &painter, int yOffset);
|
||||
void paintSelection(QPainter &painter, int messageIndex,
|
||||
Selection &selection, int yOffset);
|
||||
|
||||
// selection
|
||||
int getSelectionIndex(QPoint point);
|
||||
int getLastCharacterIndex() const;
|
||||
int getFirstMessageCharacterIndex() const;
|
||||
void addSelectionText(QString &str, int from, int to, CopyMode copymode);
|
||||
|
||||
bool isCollapsed();
|
||||
|
||||
private:
|
||||
struct Line {
|
||||
int startIndex;
|
||||
int endIndex;
|
||||
int startCharIndex;
|
||||
int endCharIndex;
|
||||
QRect rect;
|
||||
};
|
||||
|
||||
// helpers
|
||||
void _addElement(MessageLayoutElement *element, bool forceAdd = false);
|
||||
bool canCollapse();
|
||||
|
||||
// variables
|
||||
float scale_ = 1.f;
|
||||
int width_ = 0;
|
||||
MessageFlags flags_{};
|
||||
int line_ = 0;
|
||||
int height_ = 0;
|
||||
int currentX_ = 0;
|
||||
int currentY_ = 0;
|
||||
int charIndex_ = 0;
|
||||
size_t lineStart_ = 0;
|
||||
int lineHeight_ = 0;
|
||||
int spaceWidth_ = 4;
|
||||
int textLineHeight_ = 0;
|
||||
int dotdotdotWidth_ = 0;
|
||||
bool canAddMessages_ = true;
|
||||
bool isCollapsed_ = false;
|
||||
|
||||
std::vector<std::unique_ptr<MessageLayoutElement>> elements_;
|
||||
std::vector<Line> lines_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/Common.hpp"
|
||||
#include "common/FlagsEnum.hpp"
|
||||
#include "messages/Selection.hpp"
|
||||
#include "messages/layouts/MessageLayoutElement.hpp"
|
||||
|
||||
class QPainter;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum class MessageFlag : uint32_t;
|
||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||
|
||||
struct Margin {
|
||||
int top;
|
||||
int right;
|
||||
int bottom;
|
||||
int left;
|
||||
|
||||
Margin()
|
||||
: Margin(0)
|
||||
{
|
||||
}
|
||||
|
||||
Margin(int value)
|
||||
: Margin(value, value, value, value)
|
||||
{
|
||||
}
|
||||
|
||||
Margin(int _top, int _right, int _bottom, int _left)
|
||||
: top(_top)
|
||||
, right(_right)
|
||||
, bottom(_bottom)
|
||||
, left(_left)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct MessageLayoutContainer {
|
||||
MessageLayoutContainer() = default;
|
||||
|
||||
Margin margin = {4, 8, 4, 8};
|
||||
bool centered = false;
|
||||
bool enableCompactEmotes = false;
|
||||
|
||||
int getHeight() const;
|
||||
int getWidth() const;
|
||||
float getScale() const;
|
||||
|
||||
// methods
|
||||
void begin(int width_, float scale_, MessageFlags flags_);
|
||||
void end();
|
||||
|
||||
void clear();
|
||||
bool canAddElements();
|
||||
void addElement(MessageLayoutElement *element);
|
||||
void addElementNoLineBreak(MessageLayoutElement *element);
|
||||
void breakLine();
|
||||
bool atStartOfLine();
|
||||
bool fitsInLine(int width_);
|
||||
MessageLayoutElement *getElementAt(QPoint point);
|
||||
|
||||
// painting
|
||||
void paintElements(QPainter &painter);
|
||||
void paintAnimatedElements(QPainter &painter, int yOffset);
|
||||
void paintSelection(QPainter &painter, int messageIndex,
|
||||
Selection &selection, int yOffset);
|
||||
|
||||
// selection
|
||||
int getSelectionIndex(QPoint point);
|
||||
int getLastCharacterIndex() const;
|
||||
int getFirstMessageCharacterIndex() const;
|
||||
void addSelectionText(QString &str, int from, int to, CopyMode copymode);
|
||||
|
||||
bool isCollapsed();
|
||||
|
||||
private:
|
||||
struct Line {
|
||||
int startIndex;
|
||||
int endIndex;
|
||||
int startCharIndex;
|
||||
int endCharIndex;
|
||||
QRect rect;
|
||||
};
|
||||
|
||||
// helpers
|
||||
void _addElement(MessageLayoutElement *element, bool forceAdd = false);
|
||||
bool canCollapse();
|
||||
|
||||
// variables
|
||||
float scale_ = 1.f;
|
||||
int width_ = 0;
|
||||
MessageFlags flags_{};
|
||||
int line_ = 0;
|
||||
int height_ = 0;
|
||||
int currentX_ = 0;
|
||||
int currentY_ = 0;
|
||||
int charIndex_ = 0;
|
||||
size_t lineStart_ = 0;
|
||||
int lineHeight_ = 0;
|
||||
int spaceWidth_ = 4;
|
||||
int textLineHeight_ = 0;
|
||||
int dotdotdotWidth_ = 0;
|
||||
bool canAddMessages_ = true;
|
||||
bool isCollapsed_ = false;
|
||||
|
||||
std::vector<std::unique_ptr<MessageLayoutElement>> elements_;
|
||||
std::vector<Line> lines_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,59 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename T>
|
||||
class NullablePtr
|
||||
{
|
||||
public:
|
||||
NullablePtr()
|
||||
: element_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
NullablePtr(T *element)
|
||||
: element_(element)
|
||||
{
|
||||
}
|
||||
|
||||
T *operator->() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return element_;
|
||||
}
|
||||
|
||||
T &operator*() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return *element_;
|
||||
}
|
||||
|
||||
T *get() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return this->element_;
|
||||
}
|
||||
|
||||
bool isNull() const
|
||||
{
|
||||
return this->element_ == nullptr;
|
||||
}
|
||||
|
||||
bool hasElement() const
|
||||
{
|
||||
return this->element_ != nullptr;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return this->hasElement();
|
||||
}
|
||||
|
||||
private:
|
||||
T *element_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename T>
|
||||
class NullablePtr
|
||||
{
|
||||
public:
|
||||
NullablePtr()
|
||||
: element_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
NullablePtr(T *element)
|
||||
: element_(element)
|
||||
{
|
||||
}
|
||||
|
||||
T *operator->() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return element_;
|
||||
}
|
||||
|
||||
T &operator*() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return *element_;
|
||||
}
|
||||
|
||||
T *get() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return this->element_;
|
||||
}
|
||||
|
||||
bool isNull() const
|
||||
{
|
||||
return this->element_ == nullptr;
|
||||
}
|
||||
|
||||
bool hasElement() const
|
||||
{
|
||||
return this->element_ != nullptr;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return this->hasElement();
|
||||
}
|
||||
|
||||
private:
|
||||
T *element_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,350 +1,350 @@
|
|||
#include "AbstractIrcServer.hpp"
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/Common.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/LimitedQueueSnapshot.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
const int RECONNECT_BASE_INTERVAL = 2000;
|
||||
// 60 falloff counter means it will try to reconnect at most every 60*2 seconds
|
||||
const int MAX_FALLOFF_COUNTER = 60;
|
||||
|
||||
AbstractIrcServer::AbstractIrcServer()
|
||||
{
|
||||
// Initialize the connections
|
||||
this->writeConnection_.reset(new IrcConnection);
|
||||
this->writeConnection_->moveToThread(
|
||||
QCoreApplication::instance()->thread());
|
||||
|
||||
QObject::connect(
|
||||
this->writeConnection_.get(), &Communi::IrcConnection::messageReceived,
|
||||
[this](auto msg) { this->writeConnectionMessageReceived(msg); });
|
||||
|
||||
// Listen to read connection message signals
|
||||
this->readConnection_.reset(new IrcConnection);
|
||||
this->readConnection_->moveToThread(QCoreApplication::instance()->thread());
|
||||
|
||||
QObject::connect(
|
||||
this->readConnection_.get(), &Communi::IrcConnection::messageReceived,
|
||||
[this](auto msg) { this->readConnectionMessageReceived(msg); });
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::privateMessageReceived,
|
||||
[this](auto msg) { this->privateMessageReceived(msg); });
|
||||
QObject::connect(
|
||||
this->readConnection_.get(), &Communi::IrcConnection::connected,
|
||||
[this] { this->onReadConnected(this->readConnection_.get()); });
|
||||
QObject::connect(
|
||||
this->writeConnection_.get(), &Communi::IrcConnection::connected,
|
||||
[this] { this->onWriteConnected(this->writeConnection_.get()); });
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::disconnected,
|
||||
[this] { this->onDisconnected(); });
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::socketError,
|
||||
[this] { this->onSocketError(); });
|
||||
|
||||
// listen to reconnect request
|
||||
this->readConnection_->reconnectRequested.connect(
|
||||
[this] { this->connect(); });
|
||||
// this->writeConnection->reconnectRequested.connect([this] {
|
||||
// this->connect(); });
|
||||
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL);
|
||||
this->reconnectTimer_.setSingleShot(true);
|
||||
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
|
||||
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL *
|
||||
this->falloffCounter_);
|
||||
|
||||
this->falloffCounter_ =
|
||||
std::min(MAX_FALLOFF_COUNTER, this->falloffCounter_ + 1);
|
||||
|
||||
if (!this->readConnection_->isConnected())
|
||||
{
|
||||
log("Trying to reconnect... {}", this->falloffCounter_);
|
||||
this->connect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void AbstractIrcServer::connect()
|
||||
{
|
||||
this->disconnect();
|
||||
|
||||
bool separateWriteConnection = this->hasSeparateWriteConnection();
|
||||
|
||||
if (separateWriteConnection)
|
||||
{
|
||||
this->initializeConnection(this->writeConnection_.get(), false, true);
|
||||
this->initializeConnection(this->readConnection_.get(), true, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->initializeConnection(this->readConnection_.get(), true, true);
|
||||
}
|
||||
|
||||
// fourtf: this should be asynchronous
|
||||
{
|
||||
std::lock_guard<std::mutex> lock1(this->connectionMutex_);
|
||||
std::lock_guard<std::mutex> lock2(this->channelMutex);
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
if (auto channel = std::shared_ptr<Channel>(weak.lock()))
|
||||
{
|
||||
this->readConnection_->sendRaw("JOIN #" + channel->getName());
|
||||
}
|
||||
}
|
||||
|
||||
this->writeConnection_->open();
|
||||
this->readConnection_->open();
|
||||
}
|
||||
|
||||
// this->onConnected();
|
||||
// possbile event: started to connect
|
||||
}
|
||||
|
||||
void AbstractIrcServer::disconnect()
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(this->connectionMutex_);
|
||||
|
||||
this->readConnection_->close();
|
||||
this->writeConnection_->close();
|
||||
}
|
||||
|
||||
void AbstractIrcServer::sendMessage(const QString &channelName,
|
||||
const QString &message)
|
||||
{
|
||||
this->sendRawMessage("PRIVMSG #" + channelName + " :" + message);
|
||||
}
|
||||
|
||||
void AbstractIrcServer::sendRawMessage(const QString &rawMessage)
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(this->connectionMutex_);
|
||||
|
||||
if (this->hasSeparateWriteConnection())
|
||||
{
|
||||
this->writeConnection_->sendRaw(rawMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->readConnection_->sendRaw(rawMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractIrcServer::writeConnectionMessageReceived(
|
||||
Communi::IrcMessage *message)
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
|
||||
const QString &dirtyChannelName)
|
||||
{
|
||||
auto channelName = this->cleanChannelName(dirtyChannelName);
|
||||
|
||||
// try get channel
|
||||
ChannelPtr chan = this->getChannelOrEmpty(channelName);
|
||||
if (chan != Channel::getEmpty())
|
||||
{
|
||||
return chan;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
// value doesn't exist
|
||||
chan = this->createChannel(channelName);
|
||||
if (!chan)
|
||||
{
|
||||
return Channel::getEmpty();
|
||||
}
|
||||
|
||||
QString clojuresInCppAreShit = channelName;
|
||||
|
||||
this->channels.insert(channelName, chan);
|
||||
chan->destroyed.connect([this, clojuresInCppAreShit] {
|
||||
// fourtf: issues when the server itself is destroyed
|
||||
|
||||
log("[AbstractIrcServer::addChannel] {} was destroyed",
|
||||
clojuresInCppAreShit);
|
||||
this->channels.remove(clojuresInCppAreShit);
|
||||
|
||||
if (this->readConnection_)
|
||||
{
|
||||
this->readConnection_->sendRaw("PART #" + clojuresInCppAreShit);
|
||||
}
|
||||
|
||||
if (this->writeConnection_)
|
||||
{
|
||||
this->writeConnection_->sendRaw("PART #" + clojuresInCppAreShit);
|
||||
}
|
||||
});
|
||||
|
||||
// join irc channel
|
||||
{
|
||||
std::lock_guard<std::mutex> lock2(this->connectionMutex_);
|
||||
|
||||
if (this->readConnection_)
|
||||
{
|
||||
this->readConnection_->sendRaw("JOIN #" + channelName);
|
||||
}
|
||||
|
||||
if (this->writeConnection_)
|
||||
{
|
||||
this->writeConnection_->sendRaw("JOIN #" + channelName);
|
||||
}
|
||||
}
|
||||
|
||||
return chan;
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> AbstractIrcServer::getChannelOrEmpty(
|
||||
const QString &dirtyChannelName)
|
||||
{
|
||||
auto channelName = this->cleanChannelName(dirtyChannelName);
|
||||
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
// try get special channel
|
||||
ChannelPtr chan = this->getCustomChannel(channelName);
|
||||
if (chan)
|
||||
{
|
||||
return chan;
|
||||
}
|
||||
|
||||
// value exists
|
||||
auto it = this->channels.find(channelName);
|
||||
if (it != this->channels.end())
|
||||
{
|
||||
chan = it.value().lock();
|
||||
|
||||
if (chan)
|
||||
{
|
||||
return chan;
|
||||
}
|
||||
}
|
||||
|
||||
return Channel::getEmpty();
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onReadConnected(IrcConnection *connection)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
auto connectedMsg = makeSystemMessage("connected");
|
||||
connectedMsg->flags.set(MessageFlag::ConnectedMessage);
|
||||
auto reconnected = makeSystemMessage("reconnected");
|
||||
reconnected->flags.set(MessageFlag::ConnectedMessage);
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
std::shared_ptr<Channel> chan = weak.lock();
|
||||
if (!chan)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
LimitedQueueSnapshot<MessagePtr> snapshot = chan->getMessageSnapshot();
|
||||
|
||||
bool replaceMessage =
|
||||
snapshot.size() > 0 && snapshot[snapshot.size() - 1]->flags.has(
|
||||
MessageFlag::DisconnectedMessage);
|
||||
|
||||
if (replaceMessage)
|
||||
{
|
||||
chan->replaceMessage(snapshot[snapshot.size() - 1], reconnected);
|
||||
continue;
|
||||
}
|
||||
|
||||
chan->addMessage(connectedMsg);
|
||||
}
|
||||
|
||||
this->falloffCounter_ = 1;
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onWriteConnected(IrcConnection *connection)
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onDisconnected()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
MessageBuilder b(systemMessage, "disconnected");
|
||||
b->flags.set(MessageFlag::DisconnectedMessage);
|
||||
auto disconnectedMsg = b.release();
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
std::shared_ptr<Channel> chan = weak.lock();
|
||||
if (!chan)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
chan->addMessage(disconnectedMsg);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onSocketError()
|
||||
{
|
||||
this->reconnectTimer_.start();
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel(
|
||||
const QString &channelName)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString AbstractIrcServer::cleanChannelName(const QString &dirtyChannelName)
|
||||
{
|
||||
return dirtyChannelName;
|
||||
}
|
||||
|
||||
void AbstractIrcServer::addFakeMessage(const QString &data)
|
||||
{
|
||||
auto fakeMessage = Communi::IrcMessage::fromData(
|
||||
data.toUtf8(), this->readConnection_.get());
|
||||
|
||||
if (fakeMessage->command() == "PRIVMSG")
|
||||
{
|
||||
this->privateMessageReceived(
|
||||
static_cast<Communi::IrcPrivateMessage *>(fakeMessage));
|
||||
}
|
||||
else
|
||||
{
|
||||
this->readConnectionMessageReceived(fakeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractIrcServer::privateMessageReceived(
|
||||
Communi::IrcPrivateMessage *message)
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractIrcServer::readConnectionMessageReceived(
|
||||
Communi::IrcMessage *message)
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractIrcServer::forEachChannel(std::function<void(ChannelPtr)> func)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
std::shared_ptr<Channel> chan = weak.lock();
|
||||
if (!chan)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
func(chan);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "AbstractIrcServer.hpp"
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/Common.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/LimitedQueueSnapshot.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
const int RECONNECT_BASE_INTERVAL = 2000;
|
||||
// 60 falloff counter means it will try to reconnect at most every 60*2 seconds
|
||||
const int MAX_FALLOFF_COUNTER = 60;
|
||||
|
||||
AbstractIrcServer::AbstractIrcServer()
|
||||
{
|
||||
// Initialize the connections
|
||||
this->writeConnection_.reset(new IrcConnection);
|
||||
this->writeConnection_->moveToThread(
|
||||
QCoreApplication::instance()->thread());
|
||||
|
||||
QObject::connect(
|
||||
this->writeConnection_.get(), &Communi::IrcConnection::messageReceived,
|
||||
[this](auto msg) { this->writeConnectionMessageReceived(msg); });
|
||||
|
||||
// Listen to read connection message signals
|
||||
this->readConnection_.reset(new IrcConnection);
|
||||
this->readConnection_->moveToThread(QCoreApplication::instance()->thread());
|
||||
|
||||
QObject::connect(
|
||||
this->readConnection_.get(), &Communi::IrcConnection::messageReceived,
|
||||
[this](auto msg) { this->readConnectionMessageReceived(msg); });
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::privateMessageReceived,
|
||||
[this](auto msg) { this->privateMessageReceived(msg); });
|
||||
QObject::connect(
|
||||
this->readConnection_.get(), &Communi::IrcConnection::connected,
|
||||
[this] { this->onReadConnected(this->readConnection_.get()); });
|
||||
QObject::connect(
|
||||
this->writeConnection_.get(), &Communi::IrcConnection::connected,
|
||||
[this] { this->onWriteConnected(this->writeConnection_.get()); });
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::disconnected,
|
||||
[this] { this->onDisconnected(); });
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::socketError,
|
||||
[this] { this->onSocketError(); });
|
||||
|
||||
// listen to reconnect request
|
||||
this->readConnection_->reconnectRequested.connect(
|
||||
[this] { this->connect(); });
|
||||
// this->writeConnection->reconnectRequested.connect([this] {
|
||||
// this->connect(); });
|
||||
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL);
|
||||
this->reconnectTimer_.setSingleShot(true);
|
||||
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
|
||||
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL *
|
||||
this->falloffCounter_);
|
||||
|
||||
this->falloffCounter_ =
|
||||
std::min(MAX_FALLOFF_COUNTER, this->falloffCounter_ + 1);
|
||||
|
||||
if (!this->readConnection_->isConnected())
|
||||
{
|
||||
log("Trying to reconnect... {}", this->falloffCounter_);
|
||||
this->connect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void AbstractIrcServer::connect()
|
||||
{
|
||||
this->disconnect();
|
||||
|
||||
bool separateWriteConnection = this->hasSeparateWriteConnection();
|
||||
|
||||
if (separateWriteConnection)
|
||||
{
|
||||
this->initializeConnection(this->writeConnection_.get(), false, true);
|
||||
this->initializeConnection(this->readConnection_.get(), true, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->initializeConnection(this->readConnection_.get(), true, true);
|
||||
}
|
||||
|
||||
// fourtf: this should be asynchronous
|
||||
{
|
||||
std::lock_guard<std::mutex> lock1(this->connectionMutex_);
|
||||
std::lock_guard<std::mutex> lock2(this->channelMutex);
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
if (auto channel = std::shared_ptr<Channel>(weak.lock()))
|
||||
{
|
||||
this->readConnection_->sendRaw("JOIN #" + channel->getName());
|
||||
}
|
||||
}
|
||||
|
||||
this->writeConnection_->open();
|
||||
this->readConnection_->open();
|
||||
}
|
||||
|
||||
// this->onConnected();
|
||||
// possbile event: started to connect
|
||||
}
|
||||
|
||||
void AbstractIrcServer::disconnect()
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(this->connectionMutex_);
|
||||
|
||||
this->readConnection_->close();
|
||||
this->writeConnection_->close();
|
||||
}
|
||||
|
||||
void AbstractIrcServer::sendMessage(const QString &channelName,
|
||||
const QString &message)
|
||||
{
|
||||
this->sendRawMessage("PRIVMSG #" + channelName + " :" + message);
|
||||
}
|
||||
|
||||
void AbstractIrcServer::sendRawMessage(const QString &rawMessage)
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(this->connectionMutex_);
|
||||
|
||||
if (this->hasSeparateWriteConnection())
|
||||
{
|
||||
this->writeConnection_->sendRaw(rawMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->readConnection_->sendRaw(rawMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractIrcServer::writeConnectionMessageReceived(
|
||||
Communi::IrcMessage *message)
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
|
||||
const QString &dirtyChannelName)
|
||||
{
|
||||
auto channelName = this->cleanChannelName(dirtyChannelName);
|
||||
|
||||
// try get channel
|
||||
ChannelPtr chan = this->getChannelOrEmpty(channelName);
|
||||
if (chan != Channel::getEmpty())
|
||||
{
|
||||
return chan;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
// value doesn't exist
|
||||
chan = this->createChannel(channelName);
|
||||
if (!chan)
|
||||
{
|
||||
return Channel::getEmpty();
|
||||
}
|
||||
|
||||
QString clojuresInCppAreShit = channelName;
|
||||
|
||||
this->channels.insert(channelName, chan);
|
||||
chan->destroyed.connect([this, clojuresInCppAreShit] {
|
||||
// fourtf: issues when the server itself is destroyed
|
||||
|
||||
log("[AbstractIrcServer::addChannel] {} was destroyed",
|
||||
clojuresInCppAreShit);
|
||||
this->channels.remove(clojuresInCppAreShit);
|
||||
|
||||
if (this->readConnection_)
|
||||
{
|
||||
this->readConnection_->sendRaw("PART #" + clojuresInCppAreShit);
|
||||
}
|
||||
|
||||
if (this->writeConnection_)
|
||||
{
|
||||
this->writeConnection_->sendRaw("PART #" + clojuresInCppAreShit);
|
||||
}
|
||||
});
|
||||
|
||||
// join irc channel
|
||||
{
|
||||
std::lock_guard<std::mutex> lock2(this->connectionMutex_);
|
||||
|
||||
if (this->readConnection_)
|
||||
{
|
||||
this->readConnection_->sendRaw("JOIN #" + channelName);
|
||||
}
|
||||
|
||||
if (this->writeConnection_)
|
||||
{
|
||||
this->writeConnection_->sendRaw("JOIN #" + channelName);
|
||||
}
|
||||
}
|
||||
|
||||
return chan;
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> AbstractIrcServer::getChannelOrEmpty(
|
||||
const QString &dirtyChannelName)
|
||||
{
|
||||
auto channelName = this->cleanChannelName(dirtyChannelName);
|
||||
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
// try get special channel
|
||||
ChannelPtr chan = this->getCustomChannel(channelName);
|
||||
if (chan)
|
||||
{
|
||||
return chan;
|
||||
}
|
||||
|
||||
// value exists
|
||||
auto it = this->channels.find(channelName);
|
||||
if (it != this->channels.end())
|
||||
{
|
||||
chan = it.value().lock();
|
||||
|
||||
if (chan)
|
||||
{
|
||||
return chan;
|
||||
}
|
||||
}
|
||||
|
||||
return Channel::getEmpty();
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onReadConnected(IrcConnection *connection)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
auto connectedMsg = makeSystemMessage("connected");
|
||||
connectedMsg->flags.set(MessageFlag::ConnectedMessage);
|
||||
auto reconnected = makeSystemMessage("reconnected");
|
||||
reconnected->flags.set(MessageFlag::ConnectedMessage);
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
std::shared_ptr<Channel> chan = weak.lock();
|
||||
if (!chan)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
LimitedQueueSnapshot<MessagePtr> snapshot = chan->getMessageSnapshot();
|
||||
|
||||
bool replaceMessage =
|
||||
snapshot.size() > 0 && snapshot[snapshot.size() - 1]->flags.has(
|
||||
MessageFlag::DisconnectedMessage);
|
||||
|
||||
if (replaceMessage)
|
||||
{
|
||||
chan->replaceMessage(snapshot[snapshot.size() - 1], reconnected);
|
||||
continue;
|
||||
}
|
||||
|
||||
chan->addMessage(connectedMsg);
|
||||
}
|
||||
|
||||
this->falloffCounter_ = 1;
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onWriteConnected(IrcConnection *connection)
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onDisconnected()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
MessageBuilder b(systemMessage, "disconnected");
|
||||
b->flags.set(MessageFlag::DisconnectedMessage);
|
||||
auto disconnectedMsg = b.release();
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
std::shared_ptr<Channel> chan = weak.lock();
|
||||
if (!chan)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
chan->addMessage(disconnectedMsg);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onSocketError()
|
||||
{
|
||||
this->reconnectTimer_.start();
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel(
|
||||
const QString &channelName)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString AbstractIrcServer::cleanChannelName(const QString &dirtyChannelName)
|
||||
{
|
||||
return dirtyChannelName;
|
||||
}
|
||||
|
||||
void AbstractIrcServer::addFakeMessage(const QString &data)
|
||||
{
|
||||
auto fakeMessage = Communi::IrcMessage::fromData(
|
||||
data.toUtf8(), this->readConnection_.get());
|
||||
|
||||
if (fakeMessage->command() == "PRIVMSG")
|
||||
{
|
||||
this->privateMessageReceived(
|
||||
static_cast<Communi::IrcPrivateMessage *>(fakeMessage));
|
||||
}
|
||||
else
|
||||
{
|
||||
this->readConnectionMessageReceived(fakeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractIrcServer::privateMessageReceived(
|
||||
Communi::IrcPrivateMessage *message)
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractIrcServer::readConnectionMessageReceived(
|
||||
Communi::IrcMessage *message)
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractIrcServer::forEachChannel(std::function<void(ChannelPtr)> func)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
std::shared_ptr<Channel> chan = weak.lock();
|
||||
if (!chan)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
func(chan);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,83 +1,83 @@
|
|||
#pragma once
|
||||
|
||||
#include "providers/irc/IrcConnection2.hpp"
|
||||
|
||||
#include <IrcMessage>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Channel;
|
||||
using ChannelPtr = std::shared_ptr<Channel>;
|
||||
|
||||
class AbstractIrcServer
|
||||
{
|
||||
public:
|
||||
virtual ~AbstractIrcServer() = default;
|
||||
|
||||
// connection
|
||||
void connect();
|
||||
void disconnect();
|
||||
|
||||
void sendMessage(const QString &channelName, const QString &message);
|
||||
void sendRawMessage(const QString &rawMessage);
|
||||
|
||||
// channels
|
||||
std::shared_ptr<Channel> getOrAddChannel(const QString &dirtyChannelName);
|
||||
std::shared_ptr<Channel> getChannelOrEmpty(const QString &dirtyChannelName);
|
||||
|
||||
// signals
|
||||
pajlada::Signals::NoArgSignal connected;
|
||||
pajlada::Signals::NoArgSignal disconnected;
|
||||
// pajlada::Signals::Signal<Communi::IrcPrivateMessage *>
|
||||
// onPrivateMessage;
|
||||
|
||||
void addFakeMessage(const QString &data);
|
||||
|
||||
// iteration
|
||||
void forEachChannel(std::function<void(ChannelPtr)> func);
|
||||
|
||||
protected:
|
||||
AbstractIrcServer();
|
||||
|
||||
virtual void initializeConnection(IrcConnection *connection, bool isRead,
|
||||
bool isWrite) = 0;
|
||||
virtual std::shared_ptr<Channel> createChannel(
|
||||
const QString &channelName) = 0;
|
||||
|
||||
virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
|
||||
virtual void readConnectionMessageReceived(Communi::IrcMessage *message);
|
||||
virtual void writeConnectionMessageReceived(Communi::IrcMessage *message);
|
||||
|
||||
virtual void onReadConnected(IrcConnection *connection);
|
||||
virtual void onWriteConnected(IrcConnection *connection);
|
||||
virtual void onDisconnected();
|
||||
virtual void onSocketError();
|
||||
|
||||
virtual std::shared_ptr<Channel> getCustomChannel(
|
||||
const QString &channelName);
|
||||
|
||||
virtual bool hasSeparateWriteConnection() const = 0;
|
||||
virtual QString cleanChannelName(const QString &dirtyChannelName);
|
||||
|
||||
QMap<QString, std::weak_ptr<Channel>> channels;
|
||||
std::mutex channelMutex;
|
||||
|
||||
private:
|
||||
void initConnection();
|
||||
|
||||
std::unique_ptr<IrcConnection> writeConnection_ = nullptr;
|
||||
std::unique_ptr<IrcConnection> readConnection_ = nullptr;
|
||||
|
||||
QTimer reconnectTimer_;
|
||||
int falloffCounter_ = 1;
|
||||
|
||||
std::mutex connectionMutex_;
|
||||
|
||||
// bool autoReconnect_ = false;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "providers/irc/IrcConnection2.hpp"
|
||||
|
||||
#include <IrcMessage>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Channel;
|
||||
using ChannelPtr = std::shared_ptr<Channel>;
|
||||
|
||||
class AbstractIrcServer
|
||||
{
|
||||
public:
|
||||
virtual ~AbstractIrcServer() = default;
|
||||
|
||||
// connection
|
||||
void connect();
|
||||
void disconnect();
|
||||
|
||||
void sendMessage(const QString &channelName, const QString &message);
|
||||
void sendRawMessage(const QString &rawMessage);
|
||||
|
||||
// channels
|
||||
std::shared_ptr<Channel> getOrAddChannel(const QString &dirtyChannelName);
|
||||
std::shared_ptr<Channel> getChannelOrEmpty(const QString &dirtyChannelName);
|
||||
|
||||
// signals
|
||||
pajlada::Signals::NoArgSignal connected;
|
||||
pajlada::Signals::NoArgSignal disconnected;
|
||||
// pajlada::Signals::Signal<Communi::IrcPrivateMessage *>
|
||||
// onPrivateMessage;
|
||||
|
||||
void addFakeMessage(const QString &data);
|
||||
|
||||
// iteration
|
||||
void forEachChannel(std::function<void(ChannelPtr)> func);
|
||||
|
||||
protected:
|
||||
AbstractIrcServer();
|
||||
|
||||
virtual void initializeConnection(IrcConnection *connection, bool isRead,
|
||||
bool isWrite) = 0;
|
||||
virtual std::shared_ptr<Channel> createChannel(
|
||||
const QString &channelName) = 0;
|
||||
|
||||
virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
|
||||
virtual void readConnectionMessageReceived(Communi::IrcMessage *message);
|
||||
virtual void writeConnectionMessageReceived(Communi::IrcMessage *message);
|
||||
|
||||
virtual void onReadConnected(IrcConnection *connection);
|
||||
virtual void onWriteConnected(IrcConnection *connection);
|
||||
virtual void onDisconnected();
|
||||
virtual void onSocketError();
|
||||
|
||||
virtual std::shared_ptr<Channel> getCustomChannel(
|
||||
const QString &channelName);
|
||||
|
||||
virtual bool hasSeparateWriteConnection() const = 0;
|
||||
virtual QString cleanChannelName(const QString &dirtyChannelName);
|
||||
|
||||
QMap<QString, std::weak_ptr<Channel>> channels;
|
||||
std::mutex channelMutex;
|
||||
|
||||
private:
|
||||
void initConnection();
|
||||
|
||||
std::unique_ptr<IrcConnection> writeConnection_ = nullptr;
|
||||
std::unique_ptr<IrcConnection> readConnection_ = nullptr;
|
||||
|
||||
QTimer reconnectTimer_;
|
||||
int falloffCounter_ = 1;
|
||||
|
||||
std::mutex connectionMutex_;
|
||||
|
||||
// bool autoReconnect_ = false;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
#include "IrcChannel2.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// IrcChannel::IrcChannel()
|
||||
//{
|
||||
//}
|
||||
//
|
||||
} // namespace chatterino
|
||||
#include "IrcChannel2.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// IrcChannel::IrcChannel()
|
||||
//{
|
||||
//}
|
||||
//
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// class IrcChannel
|
||||
//{
|
||||
// public:
|
||||
// IrcChannel();
|
||||
//};
|
||||
//
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// class IrcChannel
|
||||
//{
|
||||
// public:
|
||||
// IrcChannel();
|
||||
//};
|
||||
//
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,44 +1,44 @@
|
|||
#include "IrcConnection2.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
IrcConnection::IrcConnection(QObject *parent)
|
||||
: Communi::IrcConnection(parent)
|
||||
{
|
||||
// send ping every x seconds
|
||||
this->pingTimer_.setInterval(5000);
|
||||
this->pingTimer_.start();
|
||||
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
|
||||
if (this->isConnected())
|
||||
{
|
||||
if (!this->recentlyReceivedMessage_.load())
|
||||
{
|
||||
this->sendRaw("PING");
|
||||
this->reconnectTimer_.start();
|
||||
}
|
||||
this->recentlyReceivedMessage_ = false;
|
||||
}
|
||||
});
|
||||
|
||||
// reconnect after x seconds without receiving a message
|
||||
this->reconnectTimer_.setInterval(5000);
|
||||
this->reconnectTimer_.setSingleShot(true);
|
||||
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
|
||||
if (this->isConnected())
|
||||
{
|
||||
reconnectRequested.invoke();
|
||||
}
|
||||
});
|
||||
|
||||
QObject::connect(this, &Communi::IrcConnection::messageReceived,
|
||||
[this](Communi::IrcMessage *) {
|
||||
this->recentlyReceivedMessage_ = true;
|
||||
|
||||
if (this->reconnectTimer_.isActive())
|
||||
{
|
||||
this->reconnectTimer_.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "IrcConnection2.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
IrcConnection::IrcConnection(QObject *parent)
|
||||
: Communi::IrcConnection(parent)
|
||||
{
|
||||
// send ping every x seconds
|
||||
this->pingTimer_.setInterval(5000);
|
||||
this->pingTimer_.start();
|
||||
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
|
||||
if (this->isConnected())
|
||||
{
|
||||
if (!this->recentlyReceivedMessage_.load())
|
||||
{
|
||||
this->sendRaw("PING");
|
||||
this->reconnectTimer_.start();
|
||||
}
|
||||
this->recentlyReceivedMessage_ = false;
|
||||
}
|
||||
});
|
||||
|
||||
// reconnect after x seconds without receiving a message
|
||||
this->reconnectTimer_.setInterval(5000);
|
||||
this->reconnectTimer_.setSingleShot(true);
|
||||
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
|
||||
if (this->isConnected())
|
||||
{
|
||||
reconnectRequested.invoke();
|
||||
}
|
||||
});
|
||||
|
||||
QObject::connect(this, &Communi::IrcConnection::messageReceived,
|
||||
[this](Communi::IrcMessage *) {
|
||||
this->recentlyReceivedMessage_ = true;
|
||||
|
||||
if (this->reconnectTimer_.isActive())
|
||||
{
|
||||
this->reconnectTimer_.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
|
||||
#include <IrcConnection>
|
||||
#include <QTimer>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class IrcConnection : public Communi::IrcConnection
|
||||
{
|
||||
public:
|
||||
IrcConnection(QObject *parent = nullptr);
|
||||
|
||||
pajlada::Signals::NoArgSignal reconnectRequested;
|
||||
|
||||
private:
|
||||
QTimer pingTimer_;
|
||||
QTimer reconnectTimer_;
|
||||
std::atomic<bool> recentlyReceivedMessage_{true};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
|
||||
#include <IrcConnection>
|
||||
#include <QTimer>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class IrcConnection : public Communi::IrcConnection
|
||||
{
|
||||
public:
|
||||
IrcConnection(QObject *parent = nullptr);
|
||||
|
||||
pajlada::Signals::NoArgSignal reconnectRequested;
|
||||
|
||||
private:
|
||||
QTimer pingTimer_;
|
||||
QTimer reconnectTimer_;
|
||||
std::atomic<bool> recentlyReceivedMessage_{true};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
#include "IrcServer.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// IrcServer::IrcServer(const QString &hostname, int port)
|
||||
//{
|
||||
// this->initConnection();
|
||||
//}
|
||||
//
|
||||
} // namespace chatterino
|
||||
#include "IrcServer.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// IrcServer::IrcServer(const QString &hostname, int port)
|
||||
//{
|
||||
// this->initConnection();
|
||||
//}
|
||||
//
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "providers/irc/AbstractIrcServer.hpp"
|
||||
#include "providers/irc/IrcAccount.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// class IrcServer
|
||||
//{
|
||||
// public:
|
||||
// IrcServer(const QString &hostname, int port);
|
||||
|
||||
// void setAccount(std::shared_ptr<IrcAccount> newAccount);
|
||||
// std::shared_ptr<IrcAccount> getAccount() const;
|
||||
|
||||
// protected:
|
||||
// virtual void initializeConnection(Communi::IrcConnection *connection, bool
|
||||
// isReadConnection);
|
||||
|
||||
// virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
|
||||
// virtual void messageReceived(Communi::IrcMessage *message);
|
||||
//};
|
||||
//
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "providers/irc/AbstractIrcServer.hpp"
|
||||
#include "providers/irc/IrcAccount.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// class IrcServer
|
||||
//{
|
||||
// public:
|
||||
// IrcServer(const QString &hostname, int port);
|
||||
|
||||
// void setAccount(std::shared_ptr<IrcAccount> newAccount);
|
||||
// std::shared_ptr<IrcAccount> getAccount() const;
|
||||
|
||||
// protected:
|
||||
// virtual void initializeConnection(Communi::IrcConnection *connection, bool
|
||||
// isReadConnection);
|
||||
|
||||
// virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
|
||||
// virtual void messageReceived(Communi::IrcMessage *message);
|
||||
//};
|
||||
//
|
||||
} // namespace chatterino
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,58 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include <IrcMessage>
|
||||
#include "messages/Message.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class TwitchServer;
|
||||
class Channel;
|
||||
|
||||
class IrcMessageHandler
|
||||
{
|
||||
IrcMessageHandler() = default;
|
||||
|
||||
public:
|
||||
static IrcMessageHandler &getInstance();
|
||||
|
||||
// parseMessage parses a single IRC message into 0+ Chatterino messages
|
||||
std::vector<MessagePtr> parseMessage(Channel *channel,
|
||||
Communi::IrcMessage *message);
|
||||
|
||||
// parsePrivMessage arses a single IRC PRIVMSG into 0-1 Chatterino messages
|
||||
std::vector<MessagePtr> parsePrivMessage(
|
||||
Channel *channel, Communi::IrcPrivateMessage *message);
|
||||
void handlePrivMessage(Communi::IrcPrivateMessage *message,
|
||||
TwitchServer &server);
|
||||
|
||||
void handleRoomStateMessage(Communi::IrcMessage *message);
|
||||
void handleClearChatMessage(Communi::IrcMessage *message);
|
||||
void handleClearMessageMessage(Communi::IrcMessage *message);
|
||||
void handleUserStateMessage(Communi::IrcMessage *message);
|
||||
void handleWhisperMessage(Communi::IrcMessage *message);
|
||||
|
||||
// parseUserNoticeMessage parses a single IRC USERNOTICE message into 0+
|
||||
// chatterino messages
|
||||
std::vector<MessagePtr> parseUserNoticeMessage(
|
||||
Channel *channel, Communi::IrcMessage *message);
|
||||
void handleUserNoticeMessage(Communi::IrcMessage *message,
|
||||
TwitchServer &server);
|
||||
|
||||
void handleModeMessage(Communi::IrcMessage *message);
|
||||
|
||||
// parseNoticeMessage parses a single IRC NOTICE message into 0+ chatterino
|
||||
// messages
|
||||
std::vector<MessagePtr> parseNoticeMessage(
|
||||
Communi::IrcNoticeMessage *message);
|
||||
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
|
||||
|
||||
void handleJoinMessage(Communi::IrcMessage *message);
|
||||
void handlePartMessage(Communi::IrcMessage *message);
|
||||
|
||||
private:
|
||||
void addMessage(Communi::IrcMessage *message, const QString &target,
|
||||
const QString &content, TwitchServer &server, bool isResub,
|
||||
bool isAction);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <IrcMessage>
|
||||
#include "messages/Message.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class TwitchServer;
|
||||
class Channel;
|
||||
|
||||
class IrcMessageHandler
|
||||
{
|
||||
IrcMessageHandler() = default;
|
||||
|
||||
public:
|
||||
static IrcMessageHandler &getInstance();
|
||||
|
||||
// parseMessage parses a single IRC message into 0+ Chatterino messages
|
||||
std::vector<MessagePtr> parseMessage(Channel *channel,
|
||||
Communi::IrcMessage *message);
|
||||
|
||||
// parsePrivMessage arses a single IRC PRIVMSG into 0-1 Chatterino messages
|
||||
std::vector<MessagePtr> parsePrivMessage(
|
||||
Channel *channel, Communi::IrcPrivateMessage *message);
|
||||
void handlePrivMessage(Communi::IrcPrivateMessage *message,
|
||||
TwitchServer &server);
|
||||
|
||||
void handleRoomStateMessage(Communi::IrcMessage *message);
|
||||
void handleClearChatMessage(Communi::IrcMessage *message);
|
||||
void handleClearMessageMessage(Communi::IrcMessage *message);
|
||||
void handleUserStateMessage(Communi::IrcMessage *message);
|
||||
void handleWhisperMessage(Communi::IrcMessage *message);
|
||||
|
||||
// parseUserNoticeMessage parses a single IRC USERNOTICE message into 0+
|
||||
// chatterino messages
|
||||
std::vector<MessagePtr> parseUserNoticeMessage(
|
||||
Channel *channel, Communi::IrcMessage *message);
|
||||
void handleUserNoticeMessage(Communi::IrcMessage *message,
|
||||
TwitchServer &server);
|
||||
|
||||
void handleModeMessage(Communi::IrcMessage *message);
|
||||
|
||||
// parseNoticeMessage parses a single IRC NOTICE message into 0+ chatterino
|
||||
// messages
|
||||
std::vector<MessagePtr> parseNoticeMessage(
|
||||
Communi::IrcNoticeMessage *message);
|
||||
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
|
||||
|
||||
void handleJoinMessage(Communi::IrcMessage *message);
|
||||
void handlePartMessage(Communi::IrcMessage *message);
|
||||
|
||||
private:
|
||||
void addMessage(Communi::IrcMessage *message, const QString &target,
|
||||
const QString &content, TwitchServer &server, bool isResub,
|
||||
bool isAction);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,234 +1,234 @@
|
|||
#include "providers/twitch/TwitchAccountManager.hpp"
|
||||
|
||||
#include "common/Common.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
TwitchAccountManager::TwitchAccountManager()
|
||||
: anonymousUser_(new TwitchAccount(ANONYMOUS_USERNAME, "", "", ""))
|
||||
{
|
||||
this->currentUserChanged.connect([this] {
|
||||
auto currentUser = this->getCurrent();
|
||||
currentUser->loadIgnores();
|
||||
});
|
||||
|
||||
this->accounts.itemRemoved.connect([this](const auto &acc) { //
|
||||
this->removeUser(acc.item.get());
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent()
|
||||
{
|
||||
if (!this->currentUser_)
|
||||
{
|
||||
return this->anonymousUser_;
|
||||
}
|
||||
|
||||
return this->currentUser_;
|
||||
}
|
||||
|
||||
std::vector<QString> TwitchAccountManager::getUsernames() const
|
||||
{
|
||||
std::vector<QString> userNames;
|
||||
|
||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||
|
||||
for (const auto &user : this->accounts)
|
||||
{
|
||||
userNames.push_back(user->getUserName());
|
||||
}
|
||||
|
||||
return userNames;
|
||||
}
|
||||
|
||||
std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
|
||||
const QString &username) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||
|
||||
for (const auto &user : this->accounts)
|
||||
{
|
||||
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool TwitchAccountManager::userExists(const QString &username) const
|
||||
{
|
||||
return this->findUserByUsername(username) != nullptr;
|
||||
}
|
||||
|
||||
void TwitchAccountManager::reloadUsers()
|
||||
{
|
||||
auto keys = pajlada::Settings::SettingManager::getObjectKeys("/accounts");
|
||||
|
||||
UserData userData;
|
||||
|
||||
bool listUpdated = false;
|
||||
|
||||
for (const auto &uid : keys)
|
||||
{
|
||||
if (uid == "current")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto username = pajlada::Settings::Setting<QString>::get(
|
||||
"/accounts/" + uid + "/username");
|
||||
auto userID = pajlada::Settings::Setting<QString>::get("/accounts/" +
|
||||
uid + "/userID");
|
||||
auto clientID = pajlada::Settings::Setting<QString>::get(
|
||||
"/accounts/" + uid + "/clientID");
|
||||
auto oauthToken = pajlada::Settings::Setting<QString>::get(
|
||||
"/accounts/" + uid + "/oauthToken");
|
||||
|
||||
if (username.isEmpty() || userID.isEmpty() || clientID.isEmpty() ||
|
||||
oauthToken.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
userData.username = username.trimmed();
|
||||
userData.userID = userID.trimmed();
|
||||
userData.clientID = clientID.trimmed();
|
||||
userData.oauthToken = oauthToken.trimmed();
|
||||
|
||||
switch (this->addUser(userData))
|
||||
{
|
||||
case AddUserResponse::UserAlreadyExists:
|
||||
{
|
||||
log("User {} already exists", userData.username);
|
||||
// Do nothing
|
||||
}
|
||||
break;
|
||||
case AddUserResponse::UserValuesUpdated:
|
||||
{
|
||||
log("User {} already exists, and values updated!",
|
||||
userData.username);
|
||||
if (userData.username == this->getCurrent()->getUserName())
|
||||
{
|
||||
log("It was the current user, so we need to reconnect "
|
||||
"stuff!");
|
||||
this->currentUserChanged.invoke();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AddUserResponse::UserAdded:
|
||||
{
|
||||
log("Added user {}", userData.username);
|
||||
listUpdated = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (listUpdated)
|
||||
{
|
||||
this->userListUpdated.invoke();
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchAccountManager::load()
|
||||
{
|
||||
this->reloadUsers();
|
||||
|
||||
this->currentUsername.connect([this](const QString &newUsername) {
|
||||
auto user = this->findUserByUsername(newUsername);
|
||||
if (user)
|
||||
{
|
||||
log("[AccountManager:currentUsernameChanged] User successfully "
|
||||
"updated to {}",
|
||||
newUsername);
|
||||
this->currentUser_ = user;
|
||||
}
|
||||
else
|
||||
{
|
||||
log("[AccountManager:currentUsernameChanged] User successfully "
|
||||
"updated to anonymous");
|
||||
this->currentUser_ = this->anonymousUser_;
|
||||
}
|
||||
|
||||
this->currentUserChanged.invoke();
|
||||
});
|
||||
}
|
||||
|
||||
bool TwitchAccountManager::isLoggedIn() const
|
||||
{
|
||||
if (!this->currentUser_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Once `TwitchAccount` class has a way to check, we should also return
|
||||
// false if the credentials are incorrect
|
||||
return !this->currentUser_->isAnon();
|
||||
}
|
||||
|
||||
bool TwitchAccountManager::removeUser(TwitchAccount *account)
|
||||
{
|
||||
auto userID(account->getUserId());
|
||||
if (!userID.isEmpty())
|
||||
{
|
||||
pajlada::Settings::SettingManager::removeSetting(
|
||||
fS("/accounts/uid{}", userID));
|
||||
}
|
||||
|
||||
if (account->getUserName() == this->currentUsername)
|
||||
{
|
||||
// The user that was removed is the current user, log into the anonymous
|
||||
// user
|
||||
this->currentUsername = "";
|
||||
}
|
||||
|
||||
this->userListUpdated.invoke();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
|
||||
const TwitchAccountManager::UserData &userData)
|
||||
{
|
||||
auto previousUser = this->findUserByUsername(userData.username);
|
||||
if (previousUser)
|
||||
{
|
||||
bool userUpdated = false;
|
||||
|
||||
if (previousUser->setOAuthClient(userData.clientID))
|
||||
{
|
||||
userUpdated = true;
|
||||
}
|
||||
|
||||
if (previousUser->setOAuthToken(userData.oauthToken))
|
||||
{
|
||||
userUpdated = true;
|
||||
}
|
||||
|
||||
if (userUpdated)
|
||||
{
|
||||
return AddUserResponse::UserValuesUpdated;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AddUserResponse::UserAlreadyExists;
|
||||
}
|
||||
}
|
||||
|
||||
auto newUser =
|
||||
std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
|
||||
userData.clientID, userData.userID);
|
||||
|
||||
// std::lock_guard<std::mutex> lock(this->mutex);
|
||||
|
||||
this->accounts.insertItem(newUser);
|
||||
|
||||
return AddUserResponse::UserAdded;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "providers/twitch/TwitchAccountManager.hpp"
|
||||
|
||||
#include "common/Common.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
TwitchAccountManager::TwitchAccountManager()
|
||||
: anonymousUser_(new TwitchAccount(ANONYMOUS_USERNAME, "", "", ""))
|
||||
{
|
||||
this->currentUserChanged.connect([this] {
|
||||
auto currentUser = this->getCurrent();
|
||||
currentUser->loadIgnores();
|
||||
});
|
||||
|
||||
this->accounts.itemRemoved.connect([this](const auto &acc) { //
|
||||
this->removeUser(acc.item.get());
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent()
|
||||
{
|
||||
if (!this->currentUser_)
|
||||
{
|
||||
return this->anonymousUser_;
|
||||
}
|
||||
|
||||
return this->currentUser_;
|
||||
}
|
||||
|
||||
std::vector<QString> TwitchAccountManager::getUsernames() const
|
||||
{
|
||||
std::vector<QString> userNames;
|
||||
|
||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||
|
||||
for (const auto &user : this->accounts)
|
||||
{
|
||||
userNames.push_back(user->getUserName());
|
||||
}
|
||||
|
||||
return userNames;
|
||||
}
|
||||
|
||||
std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
|
||||
const QString &username) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||
|
||||
for (const auto &user : this->accounts)
|
||||
{
|
||||
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool TwitchAccountManager::userExists(const QString &username) const
|
||||
{
|
||||
return this->findUserByUsername(username) != nullptr;
|
||||
}
|
||||
|
||||
void TwitchAccountManager::reloadUsers()
|
||||
{
|
||||
auto keys = pajlada::Settings::SettingManager::getObjectKeys("/accounts");
|
||||
|
||||
UserData userData;
|
||||
|
||||
bool listUpdated = false;
|
||||
|
||||
for (const auto &uid : keys)
|
||||
{
|
||||
if (uid == "current")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto username = pajlada::Settings::Setting<QString>::get(
|
||||
"/accounts/" + uid + "/username");
|
||||
auto userID = pajlada::Settings::Setting<QString>::get("/accounts/" +
|
||||
uid + "/userID");
|
||||
auto clientID = pajlada::Settings::Setting<QString>::get(
|
||||
"/accounts/" + uid + "/clientID");
|
||||
auto oauthToken = pajlada::Settings::Setting<QString>::get(
|
||||
"/accounts/" + uid + "/oauthToken");
|
||||
|
||||
if (username.isEmpty() || userID.isEmpty() || clientID.isEmpty() ||
|
||||
oauthToken.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
userData.username = username.trimmed();
|
||||
userData.userID = userID.trimmed();
|
||||
userData.clientID = clientID.trimmed();
|
||||
userData.oauthToken = oauthToken.trimmed();
|
||||
|
||||
switch (this->addUser(userData))
|
||||
{
|
||||
case AddUserResponse::UserAlreadyExists:
|
||||
{
|
||||
log("User {} already exists", userData.username);
|
||||
// Do nothing
|
||||
}
|
||||
break;
|
||||
case AddUserResponse::UserValuesUpdated:
|
||||
{
|
||||
log("User {} already exists, and values updated!",
|
||||
userData.username);
|
||||
if (userData.username == this->getCurrent()->getUserName())
|
||||
{
|
||||
log("It was the current user, so we need to reconnect "
|
||||
"stuff!");
|
||||
this->currentUserChanged.invoke();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AddUserResponse::UserAdded:
|
||||
{
|
||||
log("Added user {}", userData.username);
|
||||
listUpdated = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (listUpdated)
|
||||
{
|
||||
this->userListUpdated.invoke();
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchAccountManager::load()
|
||||
{
|
||||
this->reloadUsers();
|
||||
|
||||
this->currentUsername.connect([this](const QString &newUsername) {
|
||||
auto user = this->findUserByUsername(newUsername);
|
||||
if (user)
|
||||
{
|
||||
log("[AccountManager:currentUsernameChanged] User successfully "
|
||||
"updated to {}",
|
||||
newUsername);
|
||||
this->currentUser_ = user;
|
||||
}
|
||||
else
|
||||
{
|
||||
log("[AccountManager:currentUsernameChanged] User successfully "
|
||||
"updated to anonymous");
|
||||
this->currentUser_ = this->anonymousUser_;
|
||||
}
|
||||
|
||||
this->currentUserChanged.invoke();
|
||||
});
|
||||
}
|
||||
|
||||
bool TwitchAccountManager::isLoggedIn() const
|
||||
{
|
||||
if (!this->currentUser_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Once `TwitchAccount` class has a way to check, we should also return
|
||||
// false if the credentials are incorrect
|
||||
return !this->currentUser_->isAnon();
|
||||
}
|
||||
|
||||
bool TwitchAccountManager::removeUser(TwitchAccount *account)
|
||||
{
|
||||
auto userID(account->getUserId());
|
||||
if (!userID.isEmpty())
|
||||
{
|
||||
pajlada::Settings::SettingManager::removeSetting(
|
||||
fS("/accounts/uid{}", userID));
|
||||
}
|
||||
|
||||
if (account->getUserName() == this->currentUsername)
|
||||
{
|
||||
// The user that was removed is the current user, log into the anonymous
|
||||
// user
|
||||
this->currentUsername = "";
|
||||
}
|
||||
|
||||
this->userListUpdated.invoke();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
|
||||
const TwitchAccountManager::UserData &userData)
|
||||
{
|
||||
auto previousUser = this->findUserByUsername(userData.username);
|
||||
if (previousUser)
|
||||
{
|
||||
bool userUpdated = false;
|
||||
|
||||
if (previousUser->setOAuthClient(userData.clientID))
|
||||
{
|
||||
userUpdated = true;
|
||||
}
|
||||
|
||||
if (previousUser->setOAuthToken(userData.oauthToken))
|
||||
{
|
||||
userUpdated = true;
|
||||
}
|
||||
|
||||
if (userUpdated)
|
||||
{
|
||||
return AddUserResponse::UserValuesUpdated;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AddUserResponse::UserAlreadyExists;
|
||||
}
|
||||
}
|
||||
|
||||
auto newUser =
|
||||
std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
|
||||
userData.clientID, userData.userID);
|
||||
|
||||
// std::lock_guard<std::mutex> lock(this->mutex);
|
||||
|
||||
this->accounts.insertItem(newUser);
|
||||
|
||||
return AddUserResponse::UserAdded;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,75 +1,75 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/ChatterinoSetting.hpp"
|
||||
#include "common/SignalVector.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "util/SharedPtrElementLess.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
//
|
||||
// Warning: This class is not supposed to be created directly.
|
||||
// Get yourself an instance from our friends over at
|
||||
// AccountManager.hpp
|
||||
//
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class TwitchAccount;
|
||||
class AccountController;
|
||||
|
||||
class TwitchAccountManager
|
||||
{
|
||||
TwitchAccountManager();
|
||||
|
||||
public:
|
||||
struct UserData {
|
||||
QString username;
|
||||
QString userID;
|
||||
QString clientID;
|
||||
QString oauthToken;
|
||||
};
|
||||
|
||||
// Returns the current twitchUsers, or the anonymous user if we're not
|
||||
// currently logged in
|
||||
std::shared_ptr<TwitchAccount> getCurrent();
|
||||
|
||||
std::vector<QString> getUsernames() const;
|
||||
|
||||
std::shared_ptr<TwitchAccount> findUserByUsername(
|
||||
const QString &username) const;
|
||||
bool userExists(const QString &username) const;
|
||||
|
||||
void reloadUsers();
|
||||
void load();
|
||||
|
||||
bool isLoggedIn() const;
|
||||
|
||||
pajlada::Settings::Setting<QString> currentUsername{"/accounts/current",
|
||||
""};
|
||||
pajlada::Signals::NoArgSignal currentUserChanged;
|
||||
pajlada::Signals::NoArgSignal userListUpdated;
|
||||
|
||||
SortedSignalVector<std::shared_ptr<TwitchAccount>,
|
||||
SharedPtrElementLess<TwitchAccount>>
|
||||
accounts;
|
||||
|
||||
private:
|
||||
enum class AddUserResponse {
|
||||
UserAlreadyExists,
|
||||
UserValuesUpdated,
|
||||
UserAdded,
|
||||
};
|
||||
AddUserResponse addUser(const UserData &data);
|
||||
bool removeUser(TwitchAccount *account);
|
||||
|
||||
std::shared_ptr<TwitchAccount> currentUser_;
|
||||
|
||||
std::shared_ptr<TwitchAccount> anonymousUser_;
|
||||
mutable std::mutex mutex_;
|
||||
|
||||
friend class AccountController;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "common/ChatterinoSetting.hpp"
|
||||
#include "common/SignalVector.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "util/SharedPtrElementLess.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
//
|
||||
// Warning: This class is not supposed to be created directly.
|
||||
// Get yourself an instance from our friends over at
|
||||
// AccountManager.hpp
|
||||
//
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class TwitchAccount;
|
||||
class AccountController;
|
||||
|
||||
class TwitchAccountManager
|
||||
{
|
||||
TwitchAccountManager();
|
||||
|
||||
public:
|
||||
struct UserData {
|
||||
QString username;
|
||||
QString userID;
|
||||
QString clientID;
|
||||
QString oauthToken;
|
||||
};
|
||||
|
||||
// Returns the current twitchUsers, or the anonymous user if we're not
|
||||
// currently logged in
|
||||
std::shared_ptr<TwitchAccount> getCurrent();
|
||||
|
||||
std::vector<QString> getUsernames() const;
|
||||
|
||||
std::shared_ptr<TwitchAccount> findUserByUsername(
|
||||
const QString &username) const;
|
||||
bool userExists(const QString &username) const;
|
||||
|
||||
void reloadUsers();
|
||||
void load();
|
||||
|
||||
bool isLoggedIn() const;
|
||||
|
||||
pajlada::Settings::Setting<QString> currentUsername{"/accounts/current",
|
||||
""};
|
||||
pajlada::Signals::NoArgSignal currentUserChanged;
|
||||
pajlada::Signals::NoArgSignal userListUpdated;
|
||||
|
||||
SortedSignalVector<std::shared_ptr<TwitchAccount>,
|
||||
SharedPtrElementLess<TwitchAccount>>
|
||||
accounts;
|
||||
|
||||
private:
|
||||
enum class AddUserResponse {
|
||||
UserAlreadyExists,
|
||||
UserValuesUpdated,
|
||||
UserAdded,
|
||||
};
|
||||
AddUserResponse addUser(const UserData &data);
|
||||
bool removeUser(TwitchAccount *account);
|
||||
|
||||
std::shared_ptr<TwitchAccount> currentUser_;
|
||||
|
||||
std::shared_ptr<TwitchAccount> anonymousUser_;
|
||||
mutable std::mutex mutex_;
|
||||
|
||||
friend class AccountController;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,366 +1,366 @@
|
|||
#include "TwitchServer.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Common.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/highlights/HighlightController.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/ChatroomChannel.hpp"
|
||||
#include "providers/twitch/IrcMessageHandler.hpp"
|
||||
#include "providers/twitch/PubsubClient.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchHelpers.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
|
||||
#include <IrcCommand>
|
||||
#include <cassert>
|
||||
|
||||
// using namespace Communi;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
namespace {
|
||||
bool isChatroom(const QString &channel)
|
||||
{
|
||||
if (channel.left(10) == "chatrooms:")
|
||||
{
|
||||
auto reflist = channel.splitRef(':');
|
||||
if (reflist.size() == 3)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TwitchServer::TwitchServer()
|
||||
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
|
||||
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
|
||||
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
|
||||
{
|
||||
qDebug() << "init TwitchServer";
|
||||
|
||||
this->pubsub = new PubSub;
|
||||
|
||||
// getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) {
|
||||
// this->connect(); },
|
||||
// this->signalHolder_,
|
||||
// false);
|
||||
}
|
||||
|
||||
void TwitchServer::initialize(Settings &settings, Paths &paths)
|
||||
{
|
||||
getApp()->accounts->twitch.currentUserChanged.connect(
|
||||
[this]() { postToThread([this] { this->connect(); }); });
|
||||
|
||||
this->twitchBadges.loadTwitchBadges();
|
||||
this->bttv.loadEmotes();
|
||||
this->ffz.loadEmotes();
|
||||
}
|
||||
|
||||
void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
|
||||
bool isWrite)
|
||||
{
|
||||
this->singleConnection_ = isRead == isWrite;
|
||||
|
||||
std::shared_ptr<TwitchAccount> account =
|
||||
getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
qDebug() << "logging in as" << account->getUserName();
|
||||
|
||||
QString username = account->getUserName();
|
||||
QString oauthToken = account->getOAuthToken();
|
||||
|
||||
if (!oauthToken.startsWith("oauth:"))
|
||||
{
|
||||
oauthToken.prepend("oauth:");
|
||||
}
|
||||
|
||||
connection->setUserName(username);
|
||||
connection->setNickName(username);
|
||||
connection->setRealName(username);
|
||||
|
||||
if (!account->isAnon())
|
||||
{
|
||||
connection->setPassword(oauthToken);
|
||||
}
|
||||
|
||||
connection->setSecure(true);
|
||||
|
||||
// https://dev.twitch.tv/docs/irc/guide/#connecting-to-twitch-irc
|
||||
// SSL disabled: irc://irc.chat.twitch.tv:6667
|
||||
// SSL enabled: irc://irc.chat.twitch.tv:6697
|
||||
connection->setHost("irc.chat.twitch.tv");
|
||||
connection->setPort(6697);
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> TwitchServer::createChannel(const QString &channelName)
|
||||
{
|
||||
std::shared_ptr<TwitchChannel> channel;
|
||||
if (isChatroom(channelName))
|
||||
{
|
||||
channel = std::static_pointer_cast<TwitchChannel>(
|
||||
std::shared_ptr<ChatroomChannel>(new ChatroomChannel(
|
||||
channelName, this->twitchBadges, this->bttv, this->ffz)));
|
||||
}
|
||||
else
|
||||
{
|
||||
channel = std::shared_ptr<TwitchChannel>(new TwitchChannel(
|
||||
channelName, this->twitchBadges, this->bttv, this->ffz));
|
||||
}
|
||||
channel->initialize();
|
||||
|
||||
channel->sendMessageSignal.connect(
|
||||
[this, channel = channel.get()](auto &chan, auto &msg, bool &sent) {
|
||||
this->onMessageSendRequested(channel, msg, sent);
|
||||
});
|
||||
|
||||
return std::shared_ptr<Channel>(channel);
|
||||
}
|
||||
|
||||
void TwitchServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
|
||||
{
|
||||
IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
|
||||
}
|
||||
|
||||
void TwitchServer::readConnectionMessageReceived(Communi::IrcMessage *message)
|
||||
{
|
||||
if (message->type() == Communi::IrcMessage::Type::Private)
|
||||
{
|
||||
// We already have a handler for private messages
|
||||
return;
|
||||
}
|
||||
|
||||
const QString &command = message->command();
|
||||
|
||||
auto &handler = IrcMessageHandler::getInstance();
|
||||
|
||||
// Below commands enabled through the twitch.tv/membership CAP REQ
|
||||
if (command == "MODE")
|
||||
{
|
||||
handler.handleModeMessage(message);
|
||||
}
|
||||
else if (command == "JOIN")
|
||||
{
|
||||
handler.handleJoinMessage(message);
|
||||
}
|
||||
else if (command == "PART")
|
||||
{
|
||||
handler.handlePartMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
|
||||
{
|
||||
const QString &command = message->command();
|
||||
|
||||
auto &handler = IrcMessageHandler::getInstance();
|
||||
|
||||
// Below commands enabled through the twitch.tv/commands CAP REQ
|
||||
if (command == "USERSTATE")
|
||||
{
|
||||
handler.handleUserStateMessage(message);
|
||||
}
|
||||
else if (command == "WHISPER")
|
||||
{
|
||||
handler.handleWhisperMessage(message);
|
||||
}
|
||||
else if (command == "USERNOTICE")
|
||||
{
|
||||
handler.handleUserNoticeMessage(message, *this);
|
||||
}
|
||||
else if (command == "ROOMSTATE")
|
||||
{
|
||||
handler.handleRoomStateMessage(message);
|
||||
}
|
||||
else if (command == "CLEARCHAT")
|
||||
{
|
||||
handler.handleClearChatMessage(message);
|
||||
}
|
||||
else if (command == "CLEARMSG")
|
||||
{
|
||||
handler.handleClearMessageMessage(message);
|
||||
}
|
||||
else if (command == "NOTICE")
|
||||
{
|
||||
handler.handleNoticeMessage(
|
||||
static_cast<Communi::IrcNoticeMessage *>(message));
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchServer::onReadConnected(IrcConnection *connection)
|
||||
{
|
||||
AbstractIrcServer::onReadConnected(connection);
|
||||
|
||||
// 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/
|
||||
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/membership");
|
||||
}
|
||||
|
||||
void TwitchServer::onWriteConnected(IrcConnection *connection)
|
||||
{
|
||||
AbstractIrcServer::onWriteConnected(connection);
|
||||
|
||||
// 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/
|
||||
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/commands");
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> TwitchServer::getCustomChannel(
|
||||
const QString &channelName)
|
||||
{
|
||||
if (channelName == "/whispers")
|
||||
{
|
||||
return this->whispersChannel;
|
||||
}
|
||||
|
||||
if (channelName == "/mentions")
|
||||
{
|
||||
return this->mentionsChannel;
|
||||
}
|
||||
|
||||
if (channelName == "$$$")
|
||||
{
|
||||
static auto channel =
|
||||
std::make_shared<Channel>("$$$", chatterino::Channel::Type::Misc);
|
||||
static auto getTimer = [&] {
|
||||
for (auto i = 0; i < 1000; i++)
|
||||
{
|
||||
channel->addMessage(makeSystemMessage(QString::number(i + 1)));
|
||||
}
|
||||
|
||||
auto timer = new QTimer;
|
||||
QObject::connect(timer, &QTimer::timeout, [] {
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QTime::currentTime().toString()));
|
||||
});
|
||||
timer->start(500);
|
||||
return timer;
|
||||
}();
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TwitchServer::forEachChannelAndSpecialChannels(
|
||||
std::function<void(ChannelPtr)> func)
|
||||
{
|
||||
this->forEachChannel(func);
|
||||
|
||||
func(this->whispersChannel);
|
||||
func(this->mentionsChannel);
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> TwitchServer::getChannelOrEmptyByID(
|
||||
const QString &channelId)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
for (const auto &weakChannel : this->channels)
|
||||
{
|
||||
auto channel = weakChannel.lock();
|
||||
if (!channel)
|
||||
continue;
|
||||
|
||||
auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(channel);
|
||||
if (!twitchChannel)
|
||||
continue;
|
||||
|
||||
if (twitchChannel->roomId() == channelId &&
|
||||
twitchChannel->getName().splitRef(":").size() < 3)
|
||||
{
|
||||
return twitchChannel;
|
||||
}
|
||||
}
|
||||
|
||||
return Channel::getEmpty();
|
||||
}
|
||||
|
||||
QString TwitchServer::cleanChannelName(const QString &dirtyChannelName)
|
||||
{
|
||||
return dirtyChannelName.toLower();
|
||||
}
|
||||
|
||||
bool TwitchServer::hasSeparateWriteConnection() const
|
||||
{
|
||||
return true;
|
||||
// return getSettings()->twitchSeperateWriteConnection;
|
||||
}
|
||||
|
||||
void TwitchServer::onMessageSendRequested(TwitchChannel *channel,
|
||||
const QString &message, bool &sent)
|
||||
{
|
||||
sent = false;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->lastMessageMutex_);
|
||||
|
||||
// std::queue<std::chrono::steady_clock::time_point>
|
||||
auto &lastMessage = channel->hasHighRateLimit()
|
||||
? this->lastMessageMod_
|
||||
: this->lastMessagePleb_;
|
||||
size_t maxMessageCount = channel->hasHighRateLimit() ? 99 : 19;
|
||||
auto minMessageOffset = (channel->hasHighRateLimit() ? 100ms : 1100ms);
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
// check if you are sending messages too fast
|
||||
if (!lastMessage.empty() && lastMessage.back() + minMessageOffset > now)
|
||||
{
|
||||
if (this->lastErrorTimeSpeed_ + 30s < now)
|
||||
{
|
||||
auto errorMessage =
|
||||
makeSystemMessage("sending messages too fast");
|
||||
|
||||
channel->addMessage(errorMessage);
|
||||
|
||||
this->lastErrorTimeSpeed_ = now;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// remove messages older than 30 seconds
|
||||
while (!lastMessage.empty() && lastMessage.front() + 32s < now)
|
||||
{
|
||||
lastMessage.pop();
|
||||
}
|
||||
|
||||
// check if you are sending too many messages
|
||||
if (lastMessage.size() >= maxMessageCount)
|
||||
{
|
||||
if (this->lastErrorTimeAmount_ + 30s < now)
|
||||
{
|
||||
auto errorMessage =
|
||||
makeSystemMessage("sending too many messages");
|
||||
|
||||
channel->addMessage(errorMessage);
|
||||
|
||||
this->lastErrorTimeAmount_ = now;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
lastMessage.push(now);
|
||||
}
|
||||
|
||||
this->sendMessage(channel->getName(), message);
|
||||
sent = true;
|
||||
}
|
||||
|
||||
const BttvEmotes &TwitchServer::getBttvEmotes() const
|
||||
{
|
||||
return this->bttv;
|
||||
}
|
||||
const FfzEmotes &TwitchServer::getFfzEmotes() const
|
||||
{
|
||||
return this->ffz;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "TwitchServer.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Common.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/highlights/HighlightController.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/ChatroomChannel.hpp"
|
||||
#include "providers/twitch/IrcMessageHandler.hpp"
|
||||
#include "providers/twitch/PubsubClient.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchHelpers.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
|
||||
#include <IrcCommand>
|
||||
#include <cassert>
|
||||
|
||||
// using namespace Communi;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
namespace {
|
||||
bool isChatroom(const QString &channel)
|
||||
{
|
||||
if (channel.left(10) == "chatrooms:")
|
||||
{
|
||||
auto reflist = channel.splitRef(':');
|
||||
if (reflist.size() == 3)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TwitchServer::TwitchServer()
|
||||
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
|
||||
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
|
||||
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
|
||||
{
|
||||
qDebug() << "init TwitchServer";
|
||||
|
||||
this->pubsub = new PubSub;
|
||||
|
||||
// getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) {
|
||||
// this->connect(); },
|
||||
// this->signalHolder_,
|
||||
// false);
|
||||
}
|
||||
|
||||
void TwitchServer::initialize(Settings &settings, Paths &paths)
|
||||
{
|
||||
getApp()->accounts->twitch.currentUserChanged.connect(
|
||||
[this]() { postToThread([this] { this->connect(); }); });
|
||||
|
||||
this->twitchBadges.loadTwitchBadges();
|
||||
this->bttv.loadEmotes();
|
||||
this->ffz.loadEmotes();
|
||||
}
|
||||
|
||||
void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
|
||||
bool isWrite)
|
||||
{
|
||||
this->singleConnection_ = isRead == isWrite;
|
||||
|
||||
std::shared_ptr<TwitchAccount> account =
|
||||
getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
qDebug() << "logging in as" << account->getUserName();
|
||||
|
||||
QString username = account->getUserName();
|
||||
QString oauthToken = account->getOAuthToken();
|
||||
|
||||
if (!oauthToken.startsWith("oauth:"))
|
||||
{
|
||||
oauthToken.prepend("oauth:");
|
||||
}
|
||||
|
||||
connection->setUserName(username);
|
||||
connection->setNickName(username);
|
||||
connection->setRealName(username);
|
||||
|
||||
if (!account->isAnon())
|
||||
{
|
||||
connection->setPassword(oauthToken);
|
||||
}
|
||||
|
||||
connection->setSecure(true);
|
||||
|
||||
// https://dev.twitch.tv/docs/irc/guide/#connecting-to-twitch-irc
|
||||
// SSL disabled: irc://irc.chat.twitch.tv:6667
|
||||
// SSL enabled: irc://irc.chat.twitch.tv:6697
|
||||
connection->setHost("irc.chat.twitch.tv");
|
||||
connection->setPort(6697);
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> TwitchServer::createChannel(const QString &channelName)
|
||||
{
|
||||
std::shared_ptr<TwitchChannel> channel;
|
||||
if (isChatroom(channelName))
|
||||
{
|
||||
channel = std::static_pointer_cast<TwitchChannel>(
|
||||
std::shared_ptr<ChatroomChannel>(new ChatroomChannel(
|
||||
channelName, this->twitchBadges, this->bttv, this->ffz)));
|
||||
}
|
||||
else
|
||||
{
|
||||
channel = std::shared_ptr<TwitchChannel>(new TwitchChannel(
|
||||
channelName, this->twitchBadges, this->bttv, this->ffz));
|
||||
}
|
||||
channel->initialize();
|
||||
|
||||
channel->sendMessageSignal.connect(
|
||||
[this, channel = channel.get()](auto &chan, auto &msg, bool &sent) {
|
||||
this->onMessageSendRequested(channel, msg, sent);
|
||||
});
|
||||
|
||||
return std::shared_ptr<Channel>(channel);
|
||||
}
|
||||
|
||||
void TwitchServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
|
||||
{
|
||||
IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
|
||||
}
|
||||
|
||||
void TwitchServer::readConnectionMessageReceived(Communi::IrcMessage *message)
|
||||
{
|
||||
if (message->type() == Communi::IrcMessage::Type::Private)
|
||||
{
|
||||
// We already have a handler for private messages
|
||||
return;
|
||||
}
|
||||
|
||||
const QString &command = message->command();
|
||||
|
||||
auto &handler = IrcMessageHandler::getInstance();
|
||||
|
||||
// Below commands enabled through the twitch.tv/membership CAP REQ
|
||||
if (command == "MODE")
|
||||
{
|
||||
handler.handleModeMessage(message);
|
||||
}
|
||||
else if (command == "JOIN")
|
||||
{
|
||||
handler.handleJoinMessage(message);
|
||||
}
|
||||
else if (command == "PART")
|
||||
{
|
||||
handler.handlePartMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
|
||||
{
|
||||
const QString &command = message->command();
|
||||
|
||||
auto &handler = IrcMessageHandler::getInstance();
|
||||
|
||||
// Below commands enabled through the twitch.tv/commands CAP REQ
|
||||
if (command == "USERSTATE")
|
||||
{
|
||||
handler.handleUserStateMessage(message);
|
||||
}
|
||||
else if (command == "WHISPER")
|
||||
{
|
||||
handler.handleWhisperMessage(message);
|
||||
}
|
||||
else if (command == "USERNOTICE")
|
||||
{
|
||||
handler.handleUserNoticeMessage(message, *this);
|
||||
}
|
||||
else if (command == "ROOMSTATE")
|
||||
{
|
||||
handler.handleRoomStateMessage(message);
|
||||
}
|
||||
else if (command == "CLEARCHAT")
|
||||
{
|
||||
handler.handleClearChatMessage(message);
|
||||
}
|
||||
else if (command == "CLEARMSG")
|
||||
{
|
||||
handler.handleClearMessageMessage(message);
|
||||
}
|
||||
else if (command == "NOTICE")
|
||||
{
|
||||
handler.handleNoticeMessage(
|
||||
static_cast<Communi::IrcNoticeMessage *>(message));
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchServer::onReadConnected(IrcConnection *connection)
|
||||
{
|
||||
AbstractIrcServer::onReadConnected(connection);
|
||||
|
||||
// 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/
|
||||
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/membership");
|
||||
}
|
||||
|
||||
void TwitchServer::onWriteConnected(IrcConnection *connection)
|
||||
{
|
||||
AbstractIrcServer::onWriteConnected(connection);
|
||||
|
||||
// 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/
|
||||
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/commands");
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> TwitchServer::getCustomChannel(
|
||||
const QString &channelName)
|
||||
{
|
||||
if (channelName == "/whispers")
|
||||
{
|
||||
return this->whispersChannel;
|
||||
}
|
||||
|
||||
if (channelName == "/mentions")
|
||||
{
|
||||
return this->mentionsChannel;
|
||||
}
|
||||
|
||||
if (channelName == "$$$")
|
||||
{
|
||||
static auto channel =
|
||||
std::make_shared<Channel>("$$$", chatterino::Channel::Type::Misc);
|
||||
static auto getTimer = [&] {
|
||||
for (auto i = 0; i < 1000; i++)
|
||||
{
|
||||
channel->addMessage(makeSystemMessage(QString::number(i + 1)));
|
||||
}
|
||||
|
||||
auto timer = new QTimer;
|
||||
QObject::connect(timer, &QTimer::timeout, [] {
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QTime::currentTime().toString()));
|
||||
});
|
||||
timer->start(500);
|
||||
return timer;
|
||||
}();
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TwitchServer::forEachChannelAndSpecialChannels(
|
||||
std::function<void(ChannelPtr)> func)
|
||||
{
|
||||
this->forEachChannel(func);
|
||||
|
||||
func(this->whispersChannel);
|
||||
func(this->mentionsChannel);
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> TwitchServer::getChannelOrEmptyByID(
|
||||
const QString &channelId)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
for (const auto &weakChannel : this->channels)
|
||||
{
|
||||
auto channel = weakChannel.lock();
|
||||
if (!channel)
|
||||
continue;
|
||||
|
||||
auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(channel);
|
||||
if (!twitchChannel)
|
||||
continue;
|
||||
|
||||
if (twitchChannel->roomId() == channelId &&
|
||||
twitchChannel->getName().splitRef(":").size() < 3)
|
||||
{
|
||||
return twitchChannel;
|
||||
}
|
||||
}
|
||||
|
||||
return Channel::getEmpty();
|
||||
}
|
||||
|
||||
QString TwitchServer::cleanChannelName(const QString &dirtyChannelName)
|
||||
{
|
||||
return dirtyChannelName.toLower();
|
||||
}
|
||||
|
||||
bool TwitchServer::hasSeparateWriteConnection() const
|
||||
{
|
||||
return true;
|
||||
// return getSettings()->twitchSeperateWriteConnection;
|
||||
}
|
||||
|
||||
void TwitchServer::onMessageSendRequested(TwitchChannel *channel,
|
||||
const QString &message, bool &sent)
|
||||
{
|
||||
sent = false;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->lastMessageMutex_);
|
||||
|
||||
// std::queue<std::chrono::steady_clock::time_point>
|
||||
auto &lastMessage = channel->hasHighRateLimit()
|
||||
? this->lastMessageMod_
|
||||
: this->lastMessagePleb_;
|
||||
size_t maxMessageCount = channel->hasHighRateLimit() ? 99 : 19;
|
||||
auto minMessageOffset = (channel->hasHighRateLimit() ? 100ms : 1100ms);
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
// check if you are sending messages too fast
|
||||
if (!lastMessage.empty() && lastMessage.back() + minMessageOffset > now)
|
||||
{
|
||||
if (this->lastErrorTimeSpeed_ + 30s < now)
|
||||
{
|
||||
auto errorMessage =
|
||||
makeSystemMessage("sending messages too fast");
|
||||
|
||||
channel->addMessage(errorMessage);
|
||||
|
||||
this->lastErrorTimeSpeed_ = now;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// remove messages older than 30 seconds
|
||||
while (!lastMessage.empty() && lastMessage.front() + 32s < now)
|
||||
{
|
||||
lastMessage.pop();
|
||||
}
|
||||
|
||||
// check if you are sending too many messages
|
||||
if (lastMessage.size() >= maxMessageCount)
|
||||
{
|
||||
if (this->lastErrorTimeAmount_ + 30s < now)
|
||||
{
|
||||
auto errorMessage =
|
||||
makeSystemMessage("sending too many messages");
|
||||
|
||||
channel->addMessage(errorMessage);
|
||||
|
||||
this->lastErrorTimeAmount_ = now;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
lastMessage.push(now);
|
||||
}
|
||||
|
||||
this->sendMessage(channel->getName(), message);
|
||||
sent = true;
|
||||
}
|
||||
|
||||
const BttvEmotes &TwitchServer::getBttvEmotes() const
|
||||
{
|
||||
return this->bttv;
|
||||
}
|
||||
const FfzEmotes &TwitchServer::getFfzEmotes() const
|
||||
{
|
||||
return this->ffz;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,86 +1,86 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/Atomic.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/Singleton.hpp"
|
||||
#include "pajlada/signals/signalholder.hpp"
|
||||
#include "providers/bttv/BttvEmotes.hpp"
|
||||
#include "providers/ffz/FfzEmotes.hpp"
|
||||
#include "providers/irc/AbstractIrcServer.hpp"
|
||||
#include "providers/twitch/TwitchBadges.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Settings;
|
||||
class Paths;
|
||||
class PubSub;
|
||||
class TwitchChannel;
|
||||
|
||||
class TwitchServer final : public AbstractIrcServer, public Singleton
|
||||
{
|
||||
public:
|
||||
TwitchServer();
|
||||
virtual ~TwitchServer() override = default;
|
||||
|
||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
||||
|
||||
void forEachChannelAndSpecialChannels(std::function<void(ChannelPtr)> func);
|
||||
|
||||
std::shared_ptr<Channel> getChannelOrEmptyByID(const QString &channelID);
|
||||
|
||||
Atomic<QString> lastUserThatWhisperedMe;
|
||||
|
||||
const ChannelPtr whispersChannel;
|
||||
const ChannelPtr mentionsChannel;
|
||||
IndirectChannel watchingChannel;
|
||||
|
||||
PubSub *pubsub;
|
||||
|
||||
const BttvEmotes &getBttvEmotes() const;
|
||||
const FfzEmotes &getFfzEmotes() const;
|
||||
|
||||
protected:
|
||||
virtual void initializeConnection(IrcConnection *connection, bool isRead,
|
||||
bool isWrite) override;
|
||||
virtual std::shared_ptr<Channel> createChannel(
|
||||
const QString &channelName) override;
|
||||
|
||||
virtual void privateMessageReceived(
|
||||
Communi::IrcPrivateMessage *message) override;
|
||||
virtual void readConnectionMessageReceived(
|
||||
Communi::IrcMessage *message) override;
|
||||
virtual void writeConnectionMessageReceived(
|
||||
Communi::IrcMessage *message) override;
|
||||
|
||||
virtual void onReadConnected(IrcConnection *connection) override;
|
||||
virtual void onWriteConnected(IrcConnection *connection) override;
|
||||
|
||||
virtual std::shared_ptr<Channel> getCustomChannel(
|
||||
const QString &channelname) override;
|
||||
|
||||
virtual QString cleanChannelName(const QString &dirtyChannelName) override;
|
||||
virtual bool hasSeparateWriteConnection() const override;
|
||||
|
||||
private:
|
||||
void onMessageSendRequested(TwitchChannel *channel, const QString &message,
|
||||
bool &sent);
|
||||
|
||||
std::mutex lastMessageMutex_;
|
||||
std::queue<std::chrono::steady_clock::time_point> lastMessagePleb_;
|
||||
std::queue<std::chrono::steady_clock::time_point> lastMessageMod_;
|
||||
std::chrono::steady_clock::time_point lastErrorTimeSpeed_;
|
||||
std::chrono::steady_clock::time_point lastErrorTimeAmount_;
|
||||
|
||||
bool singleConnection_ = false;
|
||||
TwitchBadges twitchBadges;
|
||||
BttvEmotes bttv;
|
||||
FfzEmotes ffz;
|
||||
|
||||
pajlada::Signals::SignalHolder signalHolder_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "common/Atomic.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/Singleton.hpp"
|
||||
#include "pajlada/signals/signalholder.hpp"
|
||||
#include "providers/bttv/BttvEmotes.hpp"
|
||||
#include "providers/ffz/FfzEmotes.hpp"
|
||||
#include "providers/irc/AbstractIrcServer.hpp"
|
||||
#include "providers/twitch/TwitchBadges.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Settings;
|
||||
class Paths;
|
||||
class PubSub;
|
||||
class TwitchChannel;
|
||||
|
||||
class TwitchServer final : public AbstractIrcServer, public Singleton
|
||||
{
|
||||
public:
|
||||
TwitchServer();
|
||||
virtual ~TwitchServer() override = default;
|
||||
|
||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
||||
|
||||
void forEachChannelAndSpecialChannels(std::function<void(ChannelPtr)> func);
|
||||
|
||||
std::shared_ptr<Channel> getChannelOrEmptyByID(const QString &channelID);
|
||||
|
||||
Atomic<QString> lastUserThatWhisperedMe;
|
||||
|
||||
const ChannelPtr whispersChannel;
|
||||
const ChannelPtr mentionsChannel;
|
||||
IndirectChannel watchingChannel;
|
||||
|
||||
PubSub *pubsub;
|
||||
|
||||
const BttvEmotes &getBttvEmotes() const;
|
||||
const FfzEmotes &getFfzEmotes() const;
|
||||
|
||||
protected:
|
||||
virtual void initializeConnection(IrcConnection *connection, bool isRead,
|
||||
bool isWrite) override;
|
||||
virtual std::shared_ptr<Channel> createChannel(
|
||||
const QString &channelName) override;
|
||||
|
||||
virtual void privateMessageReceived(
|
||||
Communi::IrcPrivateMessage *message) override;
|
||||
virtual void readConnectionMessageReceived(
|
||||
Communi::IrcMessage *message) override;
|
||||
virtual void writeConnectionMessageReceived(
|
||||
Communi::IrcMessage *message) override;
|
||||
|
||||
virtual void onReadConnected(IrcConnection *connection) override;
|
||||
virtual void onWriteConnected(IrcConnection *connection) override;
|
||||
|
||||
virtual std::shared_ptr<Channel> getCustomChannel(
|
||||
const QString &channelname) override;
|
||||
|
||||
virtual QString cleanChannelName(const QString &dirtyChannelName) override;
|
||||
virtual bool hasSeparateWriteConnection() const override;
|
||||
|
||||
private:
|
||||
void onMessageSendRequested(TwitchChannel *channel, const QString &message,
|
||||
bool &sent);
|
||||
|
||||
std::mutex lastMessageMutex_;
|
||||
std::queue<std::chrono::steady_clock::time_point> lastMessagePleb_;
|
||||
std::queue<std::chrono::steady_clock::time_point> lastMessageMod_;
|
||||
std::chrono::steady_clock::time_point lastErrorTimeSpeed_;
|
||||
std::chrono::steady_clock::time_point lastErrorTimeAmount_;
|
||||
|
||||
bool singleConnection_ = false;
|
||||
TwitchBadges twitchBadges;
|
||||
BttvEmotes bttv;
|
||||
FfzEmotes ffz;
|
||||
|
||||
pajlada::Signals::SignalHolder signalHolder_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,272 +1,272 @@
|
|||
#include "singletons/NativeMessaging.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/interprocess/ipc/message_queue.hpp>
|
||||
|
||||
namespace ipc = boost::interprocess;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
# include <QProcess>
|
||||
|
||||
# include <Windows.h>
|
||||
# include "singletons/WindowManager.hpp"
|
||||
# include "widgets/AttachedWindow.hpp"
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#define EXTENSION_ID "glknmaideaikkmemifbfkhnomoknepka"
|
||||
#define MESSAGE_SIZE 1024
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
void registerNmManifest(Paths &paths, const QString &manifestFilename,
|
||||
const QString ®istryKeyName,
|
||||
const QJsonDocument &document);
|
||||
|
||||
void registerNmHost(Paths &paths)
|
||||
{
|
||||
if (paths.isPortable())
|
||||
return;
|
||||
|
||||
auto getBaseDocument = [&] {
|
||||
QJsonObject obj;
|
||||
obj.insert("name", "com.chatterino.chatterino");
|
||||
obj.insert("description", "Browser interaction with chatterino.");
|
||||
obj.insert("path", QCoreApplication::applicationFilePath());
|
||||
obj.insert("type", "stdio");
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
// chrome
|
||||
{
|
||||
QJsonDocument document;
|
||||
|
||||
auto obj = getBaseDocument();
|
||||
QJsonArray allowed_origins_arr = {"chrome-extension://" EXTENSION_ID
|
||||
"/"};
|
||||
obj.insert("allowed_origins", allowed_origins_arr);
|
||||
document.setObject(obj);
|
||||
|
||||
registerNmManifest(paths, "/native-messaging-manifest-chrome.json",
|
||||
"HKCU\\Software\\Google\\Chrome\\NativeMessagingHost"
|
||||
"s\\com.chatterino.chatterino",
|
||||
document);
|
||||
}
|
||||
|
||||
// firefox
|
||||
{
|
||||
QJsonDocument document;
|
||||
|
||||
auto obj = getBaseDocument();
|
||||
QJsonArray allowed_extensions = {"chatterino_native@chatterino.com"};
|
||||
obj.insert("allowed_extensions", allowed_extensions);
|
||||
document.setObject(obj);
|
||||
|
||||
registerNmManifest(paths, "/native-messaging-manifest-firefox.json",
|
||||
"HKCU\\Software\\Mozilla\\NativeMessagingHosts\\com."
|
||||
"chatterino.chatterino",
|
||||
document);
|
||||
}
|
||||
}
|
||||
|
||||
void registerNmManifest(Paths &paths, const QString &manifestFilename,
|
||||
const QString ®istryKeyName,
|
||||
const QJsonDocument &document)
|
||||
{
|
||||
(void)registryKeyName;
|
||||
|
||||
// save the manifest
|
||||
QString manifestPath = paths.miscDirectory + manifestFilename;
|
||||
QFile file(manifestPath);
|
||||
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
file.write(document.toJson());
|
||||
file.flush();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// clang-format off
|
||||
QProcess::execute("REG ADD \"" + registryKeyName + "\" /ve /t REG_SZ /d \"" + manifestPath + "\" /f");
|
||||
// clang-format on
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string &getNmQueueName(Paths &paths)
|
||||
{
|
||||
static std::string name =
|
||||
"chatterino_gui" + paths.applicationFilePathHash.toStdString();
|
||||
return name;
|
||||
}
|
||||
|
||||
// CLIENT
|
||||
|
||||
void NativeMessagingClient::sendMessage(const QByteArray &array)
|
||||
{
|
||||
try
|
||||
{
|
||||
ipc::message_queue messageQueue(ipc::open_only, "chatterino_gui");
|
||||
|
||||
messageQueue.try_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::seconds(10));
|
||||
}
|
||||
catch (ipc::interprocess_exception &ex)
|
||||
{
|
||||
qDebug() << "send to gui process:" << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingClient::writeToCout(const QByteArray &array)
|
||||
{
|
||||
auto *data = array.data();
|
||||
auto size = uint32_t(array.size());
|
||||
|
||||
std::cout.write(reinterpret_cast<char *>(&size), 4);
|
||||
std::cout.write(data, size);
|
||||
std::cout.flush();
|
||||
}
|
||||
|
||||
// SERVER
|
||||
|
||||
void NativeMessagingServer::start()
|
||||
{
|
||||
this->thread.start();
|
||||
}
|
||||
|
||||
void NativeMessagingServer::ReceiverThread::run()
|
||||
{
|
||||
ipc::message_queue::remove("chatterino_gui");
|
||||
ipc::message_queue messageQueue(ipc::open_or_create, "chatterino_gui", 100,
|
||||
MESSAGE_SIZE);
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto buf = std::make_unique<char[]>(MESSAGE_SIZE);
|
||||
auto retSize = ipc::message_queue::size_type();
|
||||
auto priority = static_cast<unsigned int>(0);
|
||||
|
||||
messageQueue.receive(buf.get(), MESSAGE_SIZE, retSize, priority);
|
||||
|
||||
auto document = QJsonDocument::fromJson(
|
||||
QByteArray::fromRawData(buf.get(), retSize));
|
||||
|
||||
this->handleMessage(document.object());
|
||||
}
|
||||
catch (ipc::interprocess_exception &ex)
|
||||
{
|
||||
qDebug() << "received from gui process:" << ex.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingServer::ReceiverThread::handleMessage(
|
||||
const QJsonObject &root)
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
QString action = root.value("action").toString();
|
||||
|
||||
if (action.isNull())
|
||||
{
|
||||
qDebug() << "NM action was null";
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << root;
|
||||
|
||||
if (action == "select")
|
||||
{
|
||||
QString _type = root.value("type").toString();
|
||||
bool attach = root.value("attach").toBool();
|
||||
bool attachFullscreen = root.value("attach_fullscreen").toBool();
|
||||
QString name = root.value("name").toString();
|
||||
|
||||
#ifdef USEWINSDK
|
||||
AttachedWindow::GetArgs args;
|
||||
args.winId = root.value("winId").toString();
|
||||
args.yOffset = root.value("yOffset").toInt(-1);
|
||||
args.width = root.value("size").toObject().value("width").toInt(-1);
|
||||
args.height = root.value("size").toObject().value("height").toInt(-1);
|
||||
args.fullscreen = attachFullscreen;
|
||||
|
||||
qDebug() << args.width << args.height << args.winId;
|
||||
|
||||
if (_type.isNull() || args.winId.isNull())
|
||||
{
|
||||
qDebug() << "NM type, name or winId missing";
|
||||
attach = false;
|
||||
attachFullscreen = false;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_type == "twitch")
|
||||
{
|
||||
postToThread([=] {
|
||||
if (!name.isEmpty())
|
||||
{
|
||||
app->twitch.server->watchingChannel.reset(
|
||||
app->twitch.server->getOrAddChannel(name));
|
||||
}
|
||||
|
||||
if (attach || attachFullscreen)
|
||||
{
|
||||
#ifdef USEWINSDK
|
||||
// if (args.height != -1) {
|
||||
auto *window =
|
||||
AttachedWindow::get(::GetForegroundWindow(), args);
|
||||
if (!name.isEmpty())
|
||||
{
|
||||
window->setChannel(
|
||||
app->twitch.server->getOrAddChannel(name));
|
||||
}
|
||||
// }
|
||||
// window->show();
|
||||
#endif
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "NM unknown channel type";
|
||||
}
|
||||
}
|
||||
else if (action == "detach")
|
||||
{
|
||||
QString winId = root.value("winId").toString();
|
||||
|
||||
if (winId.isNull())
|
||||
{
|
||||
qDebug() << "NM winId missing";
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USEWINSDK
|
||||
postToThread([winId] {
|
||||
qDebug() << "NW detach";
|
||||
AttachedWindow::detach(winId);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "NM unknown action " + action;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "singletons/NativeMessaging.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/interprocess/ipc/message_queue.hpp>
|
||||
|
||||
namespace ipc = boost::interprocess;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
# include <QProcess>
|
||||
|
||||
# include <Windows.h>
|
||||
# include "singletons/WindowManager.hpp"
|
||||
# include "widgets/AttachedWindow.hpp"
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#define EXTENSION_ID "glknmaideaikkmemifbfkhnomoknepka"
|
||||
#define MESSAGE_SIZE 1024
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
void registerNmManifest(Paths &paths, const QString &manifestFilename,
|
||||
const QString ®istryKeyName,
|
||||
const QJsonDocument &document);
|
||||
|
||||
void registerNmHost(Paths &paths)
|
||||
{
|
||||
if (paths.isPortable())
|
||||
return;
|
||||
|
||||
auto getBaseDocument = [&] {
|
||||
QJsonObject obj;
|
||||
obj.insert("name", "com.chatterino.chatterino");
|
||||
obj.insert("description", "Browser interaction with chatterino.");
|
||||
obj.insert("path", QCoreApplication::applicationFilePath());
|
||||
obj.insert("type", "stdio");
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
// chrome
|
||||
{
|
||||
QJsonDocument document;
|
||||
|
||||
auto obj = getBaseDocument();
|
||||
QJsonArray allowed_origins_arr = {"chrome-extension://" EXTENSION_ID
|
||||
"/"};
|
||||
obj.insert("allowed_origins", allowed_origins_arr);
|
||||
document.setObject(obj);
|
||||
|
||||
registerNmManifest(paths, "/native-messaging-manifest-chrome.json",
|
||||
"HKCU\\Software\\Google\\Chrome\\NativeMessagingHost"
|
||||
"s\\com.chatterino.chatterino",
|
||||
document);
|
||||
}
|
||||
|
||||
// firefox
|
||||
{
|
||||
QJsonDocument document;
|
||||
|
||||
auto obj = getBaseDocument();
|
||||
QJsonArray allowed_extensions = {"chatterino_native@chatterino.com"};
|
||||
obj.insert("allowed_extensions", allowed_extensions);
|
||||
document.setObject(obj);
|
||||
|
||||
registerNmManifest(paths, "/native-messaging-manifest-firefox.json",
|
||||
"HKCU\\Software\\Mozilla\\NativeMessagingHosts\\com."
|
||||
"chatterino.chatterino",
|
||||
document);
|
||||
}
|
||||
}
|
||||
|
||||
void registerNmManifest(Paths &paths, const QString &manifestFilename,
|
||||
const QString ®istryKeyName,
|
||||
const QJsonDocument &document)
|
||||
{
|
||||
(void)registryKeyName;
|
||||
|
||||
// save the manifest
|
||||
QString manifestPath = paths.miscDirectory + manifestFilename;
|
||||
QFile file(manifestPath);
|
||||
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
file.write(document.toJson());
|
||||
file.flush();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// clang-format off
|
||||
QProcess::execute("REG ADD \"" + registryKeyName + "\" /ve /t REG_SZ /d \"" + manifestPath + "\" /f");
|
||||
// clang-format on
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string &getNmQueueName(Paths &paths)
|
||||
{
|
||||
static std::string name =
|
||||
"chatterino_gui" + paths.applicationFilePathHash.toStdString();
|
||||
return name;
|
||||
}
|
||||
|
||||
// CLIENT
|
||||
|
||||
void NativeMessagingClient::sendMessage(const QByteArray &array)
|
||||
{
|
||||
try
|
||||
{
|
||||
ipc::message_queue messageQueue(ipc::open_only, "chatterino_gui");
|
||||
|
||||
messageQueue.try_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::seconds(10));
|
||||
}
|
||||
catch (ipc::interprocess_exception &ex)
|
||||
{
|
||||
qDebug() << "send to gui process:" << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingClient::writeToCout(const QByteArray &array)
|
||||
{
|
||||
auto *data = array.data();
|
||||
auto size = uint32_t(array.size());
|
||||
|
||||
std::cout.write(reinterpret_cast<char *>(&size), 4);
|
||||
std::cout.write(data, size);
|
||||
std::cout.flush();
|
||||
}
|
||||
|
||||
// SERVER
|
||||
|
||||
void NativeMessagingServer::start()
|
||||
{
|
||||
this->thread.start();
|
||||
}
|
||||
|
||||
void NativeMessagingServer::ReceiverThread::run()
|
||||
{
|
||||
ipc::message_queue::remove("chatterino_gui");
|
||||
ipc::message_queue messageQueue(ipc::open_or_create, "chatterino_gui", 100,
|
||||
MESSAGE_SIZE);
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto buf = std::make_unique<char[]>(MESSAGE_SIZE);
|
||||
auto retSize = ipc::message_queue::size_type();
|
||||
auto priority = static_cast<unsigned int>(0);
|
||||
|
||||
messageQueue.receive(buf.get(), MESSAGE_SIZE, retSize, priority);
|
||||
|
||||
auto document = QJsonDocument::fromJson(
|
||||
QByteArray::fromRawData(buf.get(), retSize));
|
||||
|
||||
this->handleMessage(document.object());
|
||||
}
|
||||
catch (ipc::interprocess_exception &ex)
|
||||
{
|
||||
qDebug() << "received from gui process:" << ex.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingServer::ReceiverThread::handleMessage(
|
||||
const QJsonObject &root)
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
QString action = root.value("action").toString();
|
||||
|
||||
if (action.isNull())
|
||||
{
|
||||
qDebug() << "NM action was null";
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << root;
|
||||
|
||||
if (action == "select")
|
||||
{
|
||||
QString _type = root.value("type").toString();
|
||||
bool attach = root.value("attach").toBool();
|
||||
bool attachFullscreen = root.value("attach_fullscreen").toBool();
|
||||
QString name = root.value("name").toString();
|
||||
|
||||
#ifdef USEWINSDK
|
||||
AttachedWindow::GetArgs args;
|
||||
args.winId = root.value("winId").toString();
|
||||
args.yOffset = root.value("yOffset").toInt(-1);
|
||||
args.width = root.value("size").toObject().value("width").toInt(-1);
|
||||
args.height = root.value("size").toObject().value("height").toInt(-1);
|
||||
args.fullscreen = attachFullscreen;
|
||||
|
||||
qDebug() << args.width << args.height << args.winId;
|
||||
|
||||
if (_type.isNull() || args.winId.isNull())
|
||||
{
|
||||
qDebug() << "NM type, name or winId missing";
|
||||
attach = false;
|
||||
attachFullscreen = false;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_type == "twitch")
|
||||
{
|
||||
postToThread([=] {
|
||||
if (!name.isEmpty())
|
||||
{
|
||||
app->twitch.server->watchingChannel.reset(
|
||||
app->twitch.server->getOrAddChannel(name));
|
||||
}
|
||||
|
||||
if (attach || attachFullscreen)
|
||||
{
|
||||
#ifdef USEWINSDK
|
||||
// if (args.height != -1) {
|
||||
auto *window =
|
||||
AttachedWindow::get(::GetForegroundWindow(), args);
|
||||
if (!name.isEmpty())
|
||||
{
|
||||
window->setChannel(
|
||||
app->twitch.server->getOrAddChannel(name));
|
||||
}
|
||||
// }
|
||||
// window->show();
|
||||
#endif
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "NM unknown channel type";
|
||||
}
|
||||
}
|
||||
else if (action == "detach")
|
||||
{
|
||||
QString winId = root.value("winId").toString();
|
||||
|
||||
if (winId.isNull())
|
||||
{
|
||||
qDebug() << "NM winId missing";
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USEWINSDK
|
||||
postToThread([winId] {
|
||||
qDebug() << "NW detach";
|
||||
AttachedWindow::detach(winId);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "NM unknown action " + action;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <QThread>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Application;
|
||||
class Paths;
|
||||
|
||||
void registerNmHost(Paths &paths);
|
||||
std::string &getNmQueueName(Paths &paths);
|
||||
|
||||
class NativeMessagingClient final
|
||||
{
|
||||
public:
|
||||
void sendMessage(const QByteArray &array);
|
||||
void writeToCout(const QByteArray &array);
|
||||
};
|
||||
|
||||
class NativeMessagingServer final
|
||||
{
|
||||
public:
|
||||
void start();
|
||||
|
||||
private:
|
||||
class ReceiverThread : public QThread
|
||||
{
|
||||
public:
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
void handleMessage(const QJsonObject &root);
|
||||
};
|
||||
|
||||
ReceiverThread thread;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QThread>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Application;
|
||||
class Paths;
|
||||
|
||||
void registerNmHost(Paths &paths);
|
||||
std::string &getNmQueueName(Paths &paths);
|
||||
|
||||
class NativeMessagingClient final
|
||||
{
|
||||
public:
|
||||
void sendMessage(const QByteArray &array);
|
||||
void writeToCout(const QByteArray &array);
|
||||
};
|
||||
|
||||
class NativeMessagingServer final
|
||||
{
|
||||
public:
|
||||
void start();
|
||||
|
||||
private:
|
||||
class ReceiverThread : public QThread
|
||||
{
|
||||
public:
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
void handleMessage(const QJsonObject &root);
|
||||
};
|
||||
|
||||
ReceiverThread thread;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,315 +1,315 @@
|
|||
#include "Updates.hpp"
|
||||
|
||||
#include "Settings.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "common/Version.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "util/CombinePath.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QMessageBox>
|
||||
#include <QProcess>
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
QString currentBranch()
|
||||
{
|
||||
return getSettings()->betaUpdates ? "beta" : "stable";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Updates::Updates()
|
||||
: currentVersion_(CHATTERINO_VERSION)
|
||||
, updateGuideLink_("https://chatterino.com")
|
||||
{
|
||||
qDebug() << "init UpdateManager";
|
||||
}
|
||||
|
||||
Updates &Updates::getInstance()
|
||||
{
|
||||
// fourtf: don't add this class to the application class
|
||||
static Updates instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
const QString &Updates::getCurrentVersion() const
|
||||
{
|
||||
return currentVersion_;
|
||||
}
|
||||
|
||||
const QString &Updates::getOnlineVersion() const
|
||||
{
|
||||
return onlineVersion_;
|
||||
}
|
||||
|
||||
void Updates::installUpdates()
|
||||
{
|
||||
if (this->status_ != UpdateAvailable)
|
||||
{
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
QMessageBox *box = new QMessageBox(
|
||||
QMessageBox::Information, "Chatterino Update",
|
||||
"A link will open in your browser. Download and install to update.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->exec();
|
||||
QDesktopServices::openUrl(this->updateExe_);
|
||||
#elif defined Q_OS_LINUX
|
||||
QMessageBox *box =
|
||||
new QMessageBox(QMessageBox::Information, "Chatterino Update",
|
||||
"Automatic updates are currently not available on "
|
||||
"linux. Please redownload the app to update.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->exec();
|
||||
QDesktopServices::openUrl(this->updateGuideLink_);
|
||||
#elif defined Q_OS_WIN
|
||||
if (getPaths()->isPortable())
|
||||
{
|
||||
QMessageBox *box =
|
||||
new QMessageBox(QMessageBox::Information, "Chatterino Update",
|
||||
"Chatterino is downloading the update "
|
||||
"in the background and will run the "
|
||||
"updater once it is finished.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->show();
|
||||
|
||||
NetworkRequest(this->updatePortable_)
|
||||
.timeout(600000)
|
||||
.onError([this](int) -> bool {
|
||||
this->setStatus_(DownloadFailed);
|
||||
|
||||
postToThread([] {
|
||||
QMessageBox *box = new QMessageBox(
|
||||
QMessageBox::Information, "Chatterino Update",
|
||||
"Failed while trying to download the update.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->show();
|
||||
box->raise();
|
||||
});
|
||||
|
||||
return true;
|
||||
})
|
||||
.onSuccess([this](auto result) -> Outcome {
|
||||
QByteArray object = result.getData();
|
||||
auto filename =
|
||||
combinePath(getPaths()->miscDirectory, "update.zip");
|
||||
|
||||
QFile file(filename);
|
||||
file.open(QIODevice::Truncate | QIODevice::WriteOnly);
|
||||
|
||||
if (file.write(object) == -1)
|
||||
{
|
||||
this->setStatus_(WriteFileFailed);
|
||||
return Failure;
|
||||
}
|
||||
file.flush();
|
||||
file.close();
|
||||
|
||||
QProcess::startDetached(
|
||||
combinePath(QCoreApplication::applicationDirPath(),
|
||||
"updater.1/ChatterinoUpdater.exe"),
|
||||
{filename, "restart"});
|
||||
|
||||
QApplication::exit(0);
|
||||
return Success;
|
||||
})
|
||||
.execute();
|
||||
this->setStatus_(Downloading);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox *box =
|
||||
new QMessageBox(QMessageBox::Information, "Chatterino Update",
|
||||
"Chatterino is downloading the update "
|
||||
"in the background and will run the "
|
||||
"updater once it is finished.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->show();
|
||||
|
||||
NetworkRequest(this->updateExe_)
|
||||
.timeout(600000)
|
||||
.onError([this](int) -> bool {
|
||||
this->setStatus_(DownloadFailed);
|
||||
|
||||
QMessageBox *box = new QMessageBox(
|
||||
QMessageBox::Information, "Chatterino Update",
|
||||
"Failed to download the update. \n\nTry manually "
|
||||
"downloading the update.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->exec();
|
||||
return true;
|
||||
})
|
||||
.onSuccess([this](auto result) -> Outcome {
|
||||
QByteArray object = result.getData();
|
||||
auto filename =
|
||||
combinePath(getPaths()->miscDirectory, "Update.exe");
|
||||
|
||||
QFile file(filename);
|
||||
file.open(QIODevice::Truncate | QIODevice::WriteOnly);
|
||||
|
||||
if (file.write(object) == -1)
|
||||
{
|
||||
this->setStatus_(WriteFileFailed);
|
||||
QMessageBox *box = new QMessageBox(
|
||||
QMessageBox::Information, "Chatterino Update",
|
||||
"Failed to save the update file. This could be due to "
|
||||
"window settings or antivirus software.\n\nTry "
|
||||
"manually "
|
||||
"downloading the update.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->exec();
|
||||
|
||||
QDesktopServices::openUrl(this->updateExe_);
|
||||
return Failure;
|
||||
}
|
||||
file.flush();
|
||||
file.close();
|
||||
|
||||
if (QProcess::startDetached(filename))
|
||||
{
|
||||
QApplication::exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox *box = new QMessageBox(
|
||||
QMessageBox::Information, "Chatterino Update",
|
||||
"Failed to execute update binary. This could be due to "
|
||||
"window "
|
||||
"settings or antivirus software.\n\nTry manually "
|
||||
"downloading "
|
||||
"the update.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->exec();
|
||||
|
||||
QDesktopServices::openUrl(this->updateExe_);
|
||||
}
|
||||
|
||||
return Success;
|
||||
})
|
||||
.execute();
|
||||
this->setStatus_(Downloading);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Updates::checkForUpdates()
|
||||
{
|
||||
QString url =
|
||||
"https://notitia.chatterino.com/version/chatterino/" CHATTERINO_OS "/" +
|
||||
currentBranch();
|
||||
|
||||
NetworkRequest(url)
|
||||
.timeout(60000)
|
||||
.onSuccess([this](auto result) -> Outcome {
|
||||
auto object = result.parseJson();
|
||||
/// Version available on every platform
|
||||
QJsonValue version_val = object.value("version");
|
||||
|
||||
if (!version_val.isString())
|
||||
{
|
||||
this->setStatus_(SearchFailed);
|
||||
qDebug() << "error updating";
|
||||
return Failure;
|
||||
}
|
||||
|
||||
#if defined Q_OS_WIN || defined Q_OS_MACOS
|
||||
/// Windows downloads an installer for the new version
|
||||
QJsonValue updateExe_val = object.value("updateexe");
|
||||
if (!updateExe_val.isString())
|
||||
{
|
||||
this->setStatus_(SearchFailed);
|
||||
qDebug() << "error updating";
|
||||
return Failure;
|
||||
}
|
||||
this->updateExe_ = updateExe_val.toString();
|
||||
|
||||
/// Windows portable
|
||||
QJsonValue portable_val = object.value("portable_download");
|
||||
if (!portable_val.isString())
|
||||
{
|
||||
this->setStatus_(SearchFailed);
|
||||
qDebug() << "error updating";
|
||||
return Failure;
|
||||
}
|
||||
this->updatePortable_ = portable_val.toString();
|
||||
|
||||
#elif defined Q_OS_LINUX
|
||||
QJsonValue updateGuide_val = object.value("updateguide");
|
||||
if (updateGuide_val.isString())
|
||||
{
|
||||
this->updateGuideLink_ = updateGuide_val.toString();
|
||||
}
|
||||
#else
|
||||
return Failure;
|
||||
#endif
|
||||
|
||||
/// Current version
|
||||
this->onlineVersion_ = version_val.toString();
|
||||
|
||||
/// Update available :)
|
||||
if (this->currentVersion_ != this->onlineVersion_)
|
||||
{
|
||||
this->setStatus_(UpdateAvailable);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->setStatus_(NoUpdateAvailable);
|
||||
}
|
||||
return Failure;
|
||||
})
|
||||
.execute();
|
||||
this->setStatus_(Searching);
|
||||
}
|
||||
|
||||
Updates::Status Updates::getStatus() const
|
||||
{
|
||||
return this->status_;
|
||||
}
|
||||
|
||||
bool Updates::shouldShowUpdateButton() const
|
||||
{
|
||||
switch (this->getStatus())
|
||||
{
|
||||
case UpdateAvailable:
|
||||
case SearchFailed:
|
||||
case Downloading:
|
||||
case DownloadFailed:
|
||||
case WriteFileFailed:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Updates::isError() const
|
||||
{
|
||||
switch (this->getStatus())
|
||||
{
|
||||
case SearchFailed:
|
||||
case DownloadFailed:
|
||||
case WriteFileFailed:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Updates::setStatus_(Status status)
|
||||
{
|
||||
if (this->status_ != status)
|
||||
{
|
||||
this->status_ = status;
|
||||
postToThread([this, status] { this->statusUpdated.invoke(status); });
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "Updates.hpp"
|
||||
|
||||
#include "Settings.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "common/Version.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "util/CombinePath.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QMessageBox>
|
||||
#include <QProcess>
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
QString currentBranch()
|
||||
{
|
||||
return getSettings()->betaUpdates ? "beta" : "stable";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Updates::Updates()
|
||||
: currentVersion_(CHATTERINO_VERSION)
|
||||
, updateGuideLink_("https://chatterino.com")
|
||||
{
|
||||
qDebug() << "init UpdateManager";
|
||||
}
|
||||
|
||||
Updates &Updates::getInstance()
|
||||
{
|
||||
// fourtf: don't add this class to the application class
|
||||
static Updates instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
const QString &Updates::getCurrentVersion() const
|
||||
{
|
||||
return currentVersion_;
|
||||
}
|
||||
|
||||
const QString &Updates::getOnlineVersion() const
|
||||
{
|
||||
return onlineVersion_;
|
||||
}
|
||||
|
||||
void Updates::installUpdates()
|
||||
{
|
||||
if (this->status_ != UpdateAvailable)
|
||||
{
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
QMessageBox *box = new QMessageBox(
|
||||
QMessageBox::Information, "Chatterino Update",
|
||||
"A link will open in your browser. Download and install to update.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->exec();
|
||||
QDesktopServices::openUrl(this->updateExe_);
|
||||
#elif defined Q_OS_LINUX
|
||||
QMessageBox *box =
|
||||
new QMessageBox(QMessageBox::Information, "Chatterino Update",
|
||||
"Automatic updates are currently not available on "
|
||||
"linux. Please redownload the app to update.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->exec();
|
||||
QDesktopServices::openUrl(this->updateGuideLink_);
|
||||
#elif defined Q_OS_WIN
|
||||
if (getPaths()->isPortable())
|
||||
{
|
||||
QMessageBox *box =
|
||||
new QMessageBox(QMessageBox::Information, "Chatterino Update",
|
||||
"Chatterino is downloading the update "
|
||||
"in the background and will run the "
|
||||
"updater once it is finished.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->show();
|
||||
|
||||
NetworkRequest(this->updatePortable_)
|
||||
.timeout(600000)
|
||||
.onError([this](int) -> bool {
|
||||
this->setStatus_(DownloadFailed);
|
||||
|
||||
postToThread([] {
|
||||
QMessageBox *box = new QMessageBox(
|
||||
QMessageBox::Information, "Chatterino Update",
|
||||
"Failed while trying to download the update.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->show();
|
||||
box->raise();
|
||||
});
|
||||
|
||||
return true;
|
||||
})
|
||||
.onSuccess([this](auto result) -> Outcome {
|
||||
QByteArray object = result.getData();
|
||||
auto filename =
|
||||
combinePath(getPaths()->miscDirectory, "update.zip");
|
||||
|
||||
QFile file(filename);
|
||||
file.open(QIODevice::Truncate | QIODevice::WriteOnly);
|
||||
|
||||
if (file.write(object) == -1)
|
||||
{
|
||||
this->setStatus_(WriteFileFailed);
|
||||
return Failure;
|
||||
}
|
||||
file.flush();
|
||||
file.close();
|
||||
|
||||
QProcess::startDetached(
|
||||
combinePath(QCoreApplication::applicationDirPath(),
|
||||
"updater.1/ChatterinoUpdater.exe"),
|
||||
{filename, "restart"});
|
||||
|
||||
QApplication::exit(0);
|
||||
return Success;
|
||||
})
|
||||
.execute();
|
||||
this->setStatus_(Downloading);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox *box =
|
||||
new QMessageBox(QMessageBox::Information, "Chatterino Update",
|
||||
"Chatterino is downloading the update "
|
||||
"in the background and will run the "
|
||||
"updater once it is finished.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->show();
|
||||
|
||||
NetworkRequest(this->updateExe_)
|
||||
.timeout(600000)
|
||||
.onError([this](int) -> bool {
|
||||
this->setStatus_(DownloadFailed);
|
||||
|
||||
QMessageBox *box = new QMessageBox(
|
||||
QMessageBox::Information, "Chatterino Update",
|
||||
"Failed to download the update. \n\nTry manually "
|
||||
"downloading the update.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->exec();
|
||||
return true;
|
||||
})
|
||||
.onSuccess([this](auto result) -> Outcome {
|
||||
QByteArray object = result.getData();
|
||||
auto filename =
|
||||
combinePath(getPaths()->miscDirectory, "Update.exe");
|
||||
|
||||
QFile file(filename);
|
||||
file.open(QIODevice::Truncate | QIODevice::WriteOnly);
|
||||
|
||||
if (file.write(object) == -1)
|
||||
{
|
||||
this->setStatus_(WriteFileFailed);
|
||||
QMessageBox *box = new QMessageBox(
|
||||
QMessageBox::Information, "Chatterino Update",
|
||||
"Failed to save the update file. This could be due to "
|
||||
"window settings or antivirus software.\n\nTry "
|
||||
"manually "
|
||||
"downloading the update.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->exec();
|
||||
|
||||
QDesktopServices::openUrl(this->updateExe_);
|
||||
return Failure;
|
||||
}
|
||||
file.flush();
|
||||
file.close();
|
||||
|
||||
if (QProcess::startDetached(filename))
|
||||
{
|
||||
QApplication::exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox *box = new QMessageBox(
|
||||
QMessageBox::Information, "Chatterino Update",
|
||||
"Failed to execute update binary. This could be due to "
|
||||
"window "
|
||||
"settings or antivirus software.\n\nTry manually "
|
||||
"downloading "
|
||||
"the update.");
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
box->exec();
|
||||
|
||||
QDesktopServices::openUrl(this->updateExe_);
|
||||
}
|
||||
|
||||
return Success;
|
||||
})
|
||||
.execute();
|
||||
this->setStatus_(Downloading);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Updates::checkForUpdates()
|
||||
{
|
||||
QString url =
|
||||
"https://notitia.chatterino.com/version/chatterino/" CHATTERINO_OS "/" +
|
||||
currentBranch();
|
||||
|
||||
NetworkRequest(url)
|
||||
.timeout(60000)
|
||||
.onSuccess([this](auto result) -> Outcome {
|
||||
auto object = result.parseJson();
|
||||
/// Version available on every platform
|
||||
QJsonValue version_val = object.value("version");
|
||||
|
||||
if (!version_val.isString())
|
||||
{
|
||||
this->setStatus_(SearchFailed);
|
||||
qDebug() << "error updating";
|
||||
return Failure;
|
||||
}
|
||||
|
||||
#if defined Q_OS_WIN || defined Q_OS_MACOS
|
||||
/// Windows downloads an installer for the new version
|
||||
QJsonValue updateExe_val = object.value("updateexe");
|
||||
if (!updateExe_val.isString())
|
||||
{
|
||||
this->setStatus_(SearchFailed);
|
||||
qDebug() << "error updating";
|
||||
return Failure;
|
||||
}
|
||||
this->updateExe_ = updateExe_val.toString();
|
||||
|
||||
/// Windows portable
|
||||
QJsonValue portable_val = object.value("portable_download");
|
||||
if (!portable_val.isString())
|
||||
{
|
||||
this->setStatus_(SearchFailed);
|
||||
qDebug() << "error updating";
|
||||
return Failure;
|
||||
}
|
||||
this->updatePortable_ = portable_val.toString();
|
||||
|
||||
#elif defined Q_OS_LINUX
|
||||
QJsonValue updateGuide_val = object.value("updateguide");
|
||||
if (updateGuide_val.isString())
|
||||
{
|
||||
this->updateGuideLink_ = updateGuide_val.toString();
|
||||
}
|
||||
#else
|
||||
return Failure;
|
||||
#endif
|
||||
|
||||
/// Current version
|
||||
this->onlineVersion_ = version_val.toString();
|
||||
|
||||
/// Update available :)
|
||||
if (this->currentVersion_ != this->onlineVersion_)
|
||||
{
|
||||
this->setStatus_(UpdateAvailable);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->setStatus_(NoUpdateAvailable);
|
||||
}
|
||||
return Failure;
|
||||
})
|
||||
.execute();
|
||||
this->setStatus_(Searching);
|
||||
}
|
||||
|
||||
Updates::Status Updates::getStatus() const
|
||||
{
|
||||
return this->status_;
|
||||
}
|
||||
|
||||
bool Updates::shouldShowUpdateButton() const
|
||||
{
|
||||
switch (this->getStatus())
|
||||
{
|
||||
case UpdateAvailable:
|
||||
case SearchFailed:
|
||||
case Downloading:
|
||||
case DownloadFailed:
|
||||
case WriteFileFailed:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Updates::isError() const
|
||||
{
|
||||
switch (this->getStatus())
|
||||
{
|
||||
case SearchFailed:
|
||||
case DownloadFailed:
|
||||
case WriteFileFailed:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Updates::setStatus_(Status status)
|
||||
{
|
||||
if (this->status_ != status)
|
||||
{
|
||||
this->status_ = status;
|
||||
postToThread([this, status] { this->statusUpdated.invoke(status); });
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,50 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Updates
|
||||
{
|
||||
Updates();
|
||||
|
||||
public:
|
||||
enum Status {
|
||||
None,
|
||||
Searching,
|
||||
UpdateAvailable,
|
||||
NoUpdateAvailable,
|
||||
SearchFailed,
|
||||
Downloading,
|
||||
DownloadFailed,
|
||||
WriteFileFailed,
|
||||
};
|
||||
|
||||
// fourtf: don't add this class to the application class
|
||||
static Updates &getInstance();
|
||||
|
||||
void checkForUpdates();
|
||||
const QString &getCurrentVersion() const;
|
||||
const QString &getOnlineVersion() const;
|
||||
void installUpdates();
|
||||
Status getStatus() const;
|
||||
|
||||
bool shouldShowUpdateButton() const;
|
||||
bool isError() const;
|
||||
|
||||
pajlada::Signals::Signal<Status> statusUpdated;
|
||||
|
||||
private:
|
||||
QString currentVersion_;
|
||||
QString onlineVersion_;
|
||||
Status status_ = None;
|
||||
|
||||
QString updateExe_;
|
||||
QString updatePortable_;
|
||||
QString updateGuideLink_;
|
||||
|
||||
void setStatus_(Status status);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Updates
|
||||
{
|
||||
Updates();
|
||||
|
||||
public:
|
||||
enum Status {
|
||||
None,
|
||||
Searching,
|
||||
UpdateAvailable,
|
||||
NoUpdateAvailable,
|
||||
SearchFailed,
|
||||
Downloading,
|
||||
DownloadFailed,
|
||||
WriteFileFailed,
|
||||
};
|
||||
|
||||
// fourtf: don't add this class to the application class
|
||||
static Updates &getInstance();
|
||||
|
||||
void checkForUpdates();
|
||||
const QString &getCurrentVersion() const;
|
||||
const QString &getOnlineVersion() const;
|
||||
void installUpdates();
|
||||
Status getStatus() const;
|
||||
|
||||
bool shouldShowUpdateButton() const;
|
||||
bool isError() const;
|
||||
|
||||
pajlada::Signals::Signal<Status> statusUpdated;
|
||||
|
||||
private:
|
||||
QString currentVersion_;
|
||||
QString onlineVersion_;
|
||||
Status status_ = None;
|
||||
|
||||
QString updateExe_;
|
||||
QString updatePortable_;
|
||||
QString updateGuideLink_;
|
||||
|
||||
void setStatus_(Status status);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "DebugCount.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
UniqueAccess<QMap<QString, int64_t>> DebugCount::counts_;
|
||||
|
||||
} // namespace chatterino
|
||||
#include "DebugCount.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
UniqueAccess<QMap<QString, int64_t>> DebugCount::counts_;
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,67 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include <common/UniqueAccess.hpp>
|
||||
|
||||
#include <mutex>
|
||||
#include <typeinfo>
|
||||
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class DebugCount
|
||||
{
|
||||
public:
|
||||
static void increase(const QString &name)
|
||||
{
|
||||
auto counts = counts_.access();
|
||||
|
||||
auto it = counts->find(name);
|
||||
if (it == counts->end())
|
||||
{
|
||||
counts->insert(name, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
reinterpret_cast<int64_t &>(it.value())++;
|
||||
}
|
||||
}
|
||||
|
||||
static void decrease(const QString &name)
|
||||
{
|
||||
auto counts = counts_.access();
|
||||
|
||||
auto it = counts->find(name);
|
||||
if (it == counts->end())
|
||||
{
|
||||
counts->insert(name, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
reinterpret_cast<int64_t &>(it.value())--;
|
||||
}
|
||||
}
|
||||
|
||||
static QString getDebugText()
|
||||
{
|
||||
auto counts = counts_.access();
|
||||
|
||||
QString text;
|
||||
for (auto it = counts->begin(); it != counts->end(); it++)
|
||||
{
|
||||
text += it.key() + ": " + QString::number(it.value()) + "\n";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
QString toString()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private:
|
||||
static UniqueAccess<QMap<QString, int64_t>> counts_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <common/UniqueAccess.hpp>
|
||||
|
||||
#include <mutex>
|
||||
#include <typeinfo>
|
||||
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class DebugCount
|
||||
{
|
||||
public:
|
||||
static void increase(const QString &name)
|
||||
{
|
||||
auto counts = counts_.access();
|
||||
|
||||
auto it = counts->find(name);
|
||||
if (it == counts->end())
|
||||
{
|
||||
counts->insert(name, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
reinterpret_cast<int64_t &>(it.value())++;
|
||||
}
|
||||
}
|
||||
|
||||
static void decrease(const QString &name)
|
||||
{
|
||||
auto counts = counts_.access();
|
||||
|
||||
auto it = counts->find(name);
|
||||
if (it == counts->end())
|
||||
{
|
||||
counts->insert(name, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
reinterpret_cast<int64_t &>(it.value())--;
|
||||
}
|
||||
}
|
||||
|
||||
static QString getDebugText()
|
||||
{
|
||||
auto counts = counts_.access();
|
||||
|
||||
QString text;
|
||||
for (auto it = counts->begin(); it != counts->end(); it++)
|
||||
{
|
||||
text += it.key() + ": " + QString::number(it.value()) + "\n";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
QString toString()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private:
|
||||
static UniqueAccess<QMap<QString, int64_t>> counts_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,53 +1,53 @@
|
|||
#include "FormatTime.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
void appendDuration(int count, QChar &&order, QString &outString)
|
||||
{
|
||||
outString.append(QString::number(count));
|
||||
outString.append(order);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
QString formatTime(int totalSeconds)
|
||||
{
|
||||
QString res;
|
||||
|
||||
int seconds = totalSeconds % 60;
|
||||
int timeoutMinutes = totalSeconds / 60;
|
||||
int minutes = timeoutMinutes % 60;
|
||||
int timeoutHours = timeoutMinutes / 60;
|
||||
int hours = timeoutHours % 24;
|
||||
int days = timeoutHours / 24;
|
||||
if (days > 0)
|
||||
{
|
||||
appendDuration(days, 'd', res);
|
||||
}
|
||||
if (hours > 0)
|
||||
{
|
||||
if (!res.isEmpty())
|
||||
{
|
||||
res.append(" ");
|
||||
}
|
||||
appendDuration(hours, 'h', res);
|
||||
}
|
||||
if (minutes > 0)
|
||||
{
|
||||
if (!res.isEmpty())
|
||||
{
|
||||
res.append(" ");
|
||||
}
|
||||
appendDuration(minutes, 'm', res);
|
||||
}
|
||||
if (seconds > 0)
|
||||
{
|
||||
if (!res.isEmpty())
|
||||
{
|
||||
res.append(" ");
|
||||
}
|
||||
appendDuration(seconds, 's', res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "FormatTime.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
void appendDuration(int count, QChar &&order, QString &outString)
|
||||
{
|
||||
outString.append(QString::number(count));
|
||||
outString.append(order);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
QString formatTime(int totalSeconds)
|
||||
{
|
||||
QString res;
|
||||
|
||||
int seconds = totalSeconds % 60;
|
||||
int timeoutMinutes = totalSeconds / 60;
|
||||
int minutes = timeoutMinutes % 60;
|
||||
int timeoutHours = timeoutMinutes / 60;
|
||||
int hours = timeoutHours % 24;
|
||||
int days = timeoutHours / 24;
|
||||
if (days > 0)
|
||||
{
|
||||
appendDuration(days, 'd', res);
|
||||
}
|
||||
if (hours > 0)
|
||||
{
|
||||
if (!res.isEmpty())
|
||||
{
|
||||
res.append(" ");
|
||||
}
|
||||
appendDuration(hours, 'h', res);
|
||||
}
|
||||
if (minutes > 0)
|
||||
{
|
||||
if (!res.isEmpty())
|
||||
{
|
||||
res.append(" ");
|
||||
}
|
||||
appendDuration(minutes, 'm', res);
|
||||
}
|
||||
if (seconds > 0)
|
||||
{
|
||||
if (!res.isEmpty())
|
||||
{
|
||||
res.append(" ");
|
||||
}
|
||||
appendDuration(seconds, 's', res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// format: 1h 23m 42s
|
||||
QString formatTime(int totalSeconds);
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// format: 1h 23m 42s
|
||||
QString formatTime(int totalSeconds);
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,99 +1,99 @@
|
|||
#include "IncognitoBrowser.hpp"
|
||||
|
||||
#include <QProcess>
|
||||
#include <QRegularExpression>
|
||||
#include <QSettings>
|
||||
#include <QVariant>
|
||||
|
||||
#include "debug/Log.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
#ifdef Q_OS_WIN
|
||||
QString injectPrivateSwitch(QString command)
|
||||
{
|
||||
// list of command line switches to turn on private browsing in browsers
|
||||
static auto switches = std::vector<std::pair<QString, QString>>{
|
||||
{"firefox", "-private-window"}, {"chrome", "-incognito"},
|
||||
{"vivaldi", "-incognito"}, {"opera", "-newprivatetab"},
|
||||
{"opera\\\\launcher", "--private"}, {"iexplore", "-private"},
|
||||
};
|
||||
|
||||
// transform into regex and replacement string
|
||||
std::vector<std::pair<QRegularExpression, QString>> replacers;
|
||||
for (const auto &switch_ : switches)
|
||||
{
|
||||
replacers.emplace_back(
|
||||
QRegularExpression("(" + switch_.first + "\\.exe\"?).*",
|
||||
QRegularExpression::CaseInsensitiveOption),
|
||||
"\\1 " + switch_.second);
|
||||
}
|
||||
|
||||
// try to find matching regex and apply it
|
||||
for (const auto &replacement : replacers)
|
||||
{
|
||||
if (replacement.first.match(command).hasMatch())
|
||||
{
|
||||
command.replace(replacement.first, replacement.second);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
|
||||
// couldn't match any browser -> unknown browser
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString getCommand(const QString &link)
|
||||
{
|
||||
// get default browser prog id
|
||||
auto browserId = QSettings("HKEY_CURRENT_"
|
||||
"USER\\Software\\Microsoft\\Windows\\Shell\\"
|
||||
"Associations\\UrlAssociatio"
|
||||
"ns\\http\\UserChoice",
|
||||
QSettings::NativeFormat)
|
||||
.value("Progid")
|
||||
.toString();
|
||||
|
||||
// get default browser start command
|
||||
auto command = QSettings("HKEY_CLASSES_ROOT\\" + browserId +
|
||||
"\\shell\\open\\command",
|
||||
QSettings::NativeFormat)
|
||||
.value("Default")
|
||||
.toString();
|
||||
if (command.isNull())
|
||||
return QString();
|
||||
|
||||
log(command);
|
||||
|
||||
// inject switch to enable private browsing
|
||||
command = injectPrivateSwitch(command);
|
||||
if (command.isNull())
|
||||
return QString();
|
||||
|
||||
// link
|
||||
command += " " + link;
|
||||
|
||||
return command;
|
||||
}
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
bool supportsIncognitoLinks()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return !getCommand("").isNull();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void openLinkIncognito(const QString &link)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
auto command = getCommand(link);
|
||||
|
||||
QProcess::startDetached(command);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "IncognitoBrowser.hpp"
|
||||
|
||||
#include <QProcess>
|
||||
#include <QRegularExpression>
|
||||
#include <QSettings>
|
||||
#include <QVariant>
|
||||
|
||||
#include "debug/Log.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
#ifdef Q_OS_WIN
|
||||
QString injectPrivateSwitch(QString command)
|
||||
{
|
||||
// list of command line switches to turn on private browsing in browsers
|
||||
static auto switches = std::vector<std::pair<QString, QString>>{
|
||||
{"firefox", "-private-window"}, {"chrome", "-incognito"},
|
||||
{"vivaldi", "-incognito"}, {"opera", "-newprivatetab"},
|
||||
{"opera\\\\launcher", "--private"}, {"iexplore", "-private"},
|
||||
};
|
||||
|
||||
// transform into regex and replacement string
|
||||
std::vector<std::pair<QRegularExpression, QString>> replacers;
|
||||
for (const auto &switch_ : switches)
|
||||
{
|
||||
replacers.emplace_back(
|
||||
QRegularExpression("(" + switch_.first + "\\.exe\"?).*",
|
||||
QRegularExpression::CaseInsensitiveOption),
|
||||
"\\1 " + switch_.second);
|
||||
}
|
||||
|
||||
// try to find matching regex and apply it
|
||||
for (const auto &replacement : replacers)
|
||||
{
|
||||
if (replacement.first.match(command).hasMatch())
|
||||
{
|
||||
command.replace(replacement.first, replacement.second);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
|
||||
// couldn't match any browser -> unknown browser
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString getCommand(const QString &link)
|
||||
{
|
||||
// get default browser prog id
|
||||
auto browserId = QSettings("HKEY_CURRENT_"
|
||||
"USER\\Software\\Microsoft\\Windows\\Shell\\"
|
||||
"Associations\\UrlAssociatio"
|
||||
"ns\\http\\UserChoice",
|
||||
QSettings::NativeFormat)
|
||||
.value("Progid")
|
||||
.toString();
|
||||
|
||||
// get default browser start command
|
||||
auto command = QSettings("HKEY_CLASSES_ROOT\\" + browserId +
|
||||
"\\shell\\open\\command",
|
||||
QSettings::NativeFormat)
|
||||
.value("Default")
|
||||
.toString();
|
||||
if (command.isNull())
|
||||
return QString();
|
||||
|
||||
log(command);
|
||||
|
||||
// inject switch to enable private browsing
|
||||
command = injectPrivateSwitch(command);
|
||||
if (command.isNull())
|
||||
return QString();
|
||||
|
||||
// link
|
||||
command += " " + link;
|
||||
|
||||
return command;
|
||||
}
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
bool supportsIncognitoLinks()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return !getCommand("").isNull();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void openLinkIncognito(const QString &link)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
auto command = getCommand(link);
|
||||
|
||||
QProcess::startDetached(command);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
bool supportsIncognitoLinks();
|
||||
void openLinkIncognito(const QString &link);
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
bool supportsIncognitoLinks();
|
||||
void openLinkIncognito(const QString &link);
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue