diff --git a/chatterino.pro b/chatterino.pro index ec6659982..bee2ef085 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -112,6 +112,7 @@ SOURCES += \ src/controllers/commands/CommandModel.cpp \ src/controllers/highlights/HighlightController.cpp \ src/controllers/highlights/HighlightModel.cpp \ + src/controllers/highlights/HighlightBlacklistModel.cpp \ src/controllers/ignores/IgnoreController.cpp \ src/controllers/ignores/IgnoreModel.cpp \ src/controllers/taggedusers/TaggedUser.cpp \ @@ -254,7 +255,9 @@ HEADERS += \ src/controllers/commands/CommandModel.hpp \ src/controllers/highlights/HighlightController.hpp \ src/controllers/highlights/HighlightModel.hpp \ + src/controllers/highlights/HighlightBlacklistModel.hpp \ src/controllers/highlights/HighlightPhrase.hpp \ + src/controllers/highlights/HighlightBlacklistUser.hpp \ src/controllers/ignores/IgnoreController.hpp \ src/controllers/ignores/IgnoreModel.hpp \ src/controllers/ignores/IgnorePhrase.hpp \ diff --git a/src/controllers/highlights/HighlightBlacklistModel.cpp b/src/controllers/highlights/HighlightBlacklistModel.cpp new file mode 100644 index 000000000..47d095a2d --- /dev/null +++ b/src/controllers/highlights/HighlightBlacklistModel.cpp @@ -0,0 +1,33 @@ +#include "controllers/highlights/HighlightBlacklistModel.hpp" + +#include "Application.hpp" +#include "singletons/Settings.hpp" +#include "util/StandardItemHelper.hpp" + +namespace chatterino { + +// commandmodel +HighlightBlacklistModel::HighlightBlacklistModel(QObject *parent) + : SignalVectorModel(2, parent) +{ +} + +// turn a vector item into a model row +HighlightBlacklistUser HighlightBlacklistModel::getItemFromRow( + std::vector &row, const HighlightBlacklistUser &original) +{ + // key, regex + + return HighlightBlacklistUser{row[0]->data(Qt::DisplayRole).toString(), + row[1]->data(Qt::CheckStateRole).toBool()}; +} + +// turns a row in the model into a vector item +void HighlightBlacklistModel::getRowFromItem(const HighlightBlacklistUser &item, + std::vector &row) +{ + setStringItem(row[0], item.getPattern()); + setBoolItem(row[1], item.isRegex()); +} + +} // namespace chatterino diff --git a/src/controllers/highlights/HighlightBlacklistModel.hpp b/src/controllers/highlights/HighlightBlacklistModel.hpp new file mode 100644 index 000000000..494640833 --- /dev/null +++ b/src/controllers/highlights/HighlightBlacklistModel.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "common/SignalVectorModel.hpp" +#include "controllers/highlights/HighlightBlacklistUser.hpp" + +namespace chatterino { + +class HighlightController; + +class HighlightBlacklistModel : public SignalVectorModel +{ + explicit HighlightBlacklistModel(QObject *parent); + +protected: + // turn a vector item into a model row + virtual HighlightBlacklistUser getItemFromRow(std::vector &row, + const HighlightBlacklistUser &original) override; + + // turns a row in the model into a vector item + virtual void getRowFromItem(const HighlightBlacklistUser &item, + std::vector &row) override; + + friend class HighlightController; +}; + +} // namespace chatterino diff --git a/src/controllers/highlights/HighlightBlacklistUser.hpp b/src/controllers/highlights/HighlightBlacklistUser.hpp new file mode 100644 index 000000000..e56ea8d8e --- /dev/null +++ b/src/controllers/highlights/HighlightBlacklistUser.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include "common/SerializeCustom.hpp" +#include "util/RapidjsonHelpers.hpp" + +#include +#include +#include + +#include + +namespace chatterino { + +class HighlightBlacklistUser +{ + QString pattern_; + bool isRegex_; + QRegularExpression regex_; + +public: + bool operator==(const HighlightBlacklistUser &other) const + { + return std::tie(this->pattern_, this->isRegex_) == std::tie(other.pattern_, other.isRegex_); + } + + HighlightBlacklistUser(const QString &pattern, bool isRegex = false) + : pattern_(pattern) + , isRegex_(isRegex) + , regex_(isRegex ? pattern : "", QRegularExpression::CaseInsensitiveOption | + QRegularExpression::UseUnicodePropertiesOption) + { + } + + const QString &getPattern() const + { + return this->pattern_; + } + + bool isRegex() const + { + return this->isRegex_; + } + + bool isValidRegex() const + { + return this->isRegex() && this->regex_.isValid(); + } + + bool isMatch(const QString &subject) const + { + if (this->isRegex()) { + if (this->isValidRegex()) { + return this->regex_.match(subject).hasMatch(); + } + + return false; + } + + return subject.toLower() == this->pattern_.toLower(); + } +}; + +} // namespace chatterino + +namespace pajlada { +namespace Settings { + +template <> +struct Serialize { + static rapidjson::Value get(const chatterino::HighlightBlacklistUser &value, + rapidjson::Document::AllocatorType &a) + { + rapidjson::Value ret(rapidjson::kObjectType); + + AddMember(ret, "pattern", value.getPattern(), a); + AddMember(ret, "regex", value.isRegex(), a); + + return ret; + } +}; + +template <> +struct Deserialize { + static chatterino::HighlightBlacklistUser get(const rapidjson::Value &value) + { + QString pattern; + bool isRegex = false; + + if (!value.IsObject()) { + return chatterino::HighlightBlacklistUser(pattern, isRegex); + } + + chatterino::rj::getSafe(value, "pattern", pattern); + chatterino::rj::getSafe(value, "regex", isRegex); + + return chatterino::HighlightBlacklistUser(pattern, isRegex); + } +}; + +} // namespace Settings +} // namespace pajlada diff --git a/src/controllers/highlights/HighlightController.cpp b/src/controllers/highlights/HighlightController.cpp index 1f8c8fa00..2e49dd34d 100644 --- a/src/controllers/highlights/HighlightController.cpp +++ b/src/controllers/highlights/HighlightController.cpp @@ -1,6 +1,7 @@ #include "HighlightController.hpp" #include "Application.hpp" +#include "controllers/highlights/HighlightBlacklistModel.hpp" #include "controllers/highlights/HighlightModel.hpp" #include "widgets/dialogs/NotificationPopup.hpp" @@ -32,6 +33,26 @@ HighlightModel *HighlightController::createModel(QObject *parent) return model; } +HighlightBlacklistModel *HighlightController::createBlacklistModel(QObject *parent) +{ + auto *model = new HighlightBlacklistModel(parent); + model->init(&this->blacklistedUsers); + + return model; +} + +bool HighlightController::blacklistContains(const QString &username) +{ + std::vector blacklistItems = this->blacklistedUsers.getVector(); + for (const auto &blacklistedUser : blacklistItems) { + if (blacklistedUser.isMatch(username)) { + return true; + } + } + + return false; +} + void HighlightController::addHighlight(const MessagePtr &msg) { // static NotificationPopup popup; diff --git a/src/controllers/highlights/HighlightController.hpp b/src/controllers/highlights/HighlightController.hpp index 2fcaa3fff..916641455 100644 --- a/src/controllers/highlights/HighlightController.hpp +++ b/src/controllers/highlights/HighlightController.hpp @@ -1,6 +1,7 @@ #pragma once #include "common/SignalVector.hpp" +#include "controllers/highlights/HighlightBlacklistUser.hpp" #include "controllers/highlights/HighlightPhrase.hpp" #include "messages/Message.hpp" #include "singletons/Settings.hpp" @@ -8,6 +9,7 @@ namespace chatterino { class HighlightModel; +class HighlightBlacklistModel; class HighlightController { @@ -17,8 +19,12 @@ public: void initialize(); UnsortedSignalVector phrases; + UnsortedSignalVector blacklistedUsers; HighlightModel *createModel(QObject *parent); + HighlightBlacklistModel *createBlacklistModel(QObject *parent); + + bool blacklistContains(const QString &username); void addHighlight(const MessagePtr &msg); @@ -27,6 +33,7 @@ private: ChatterinoSetting> highlightsSetting = { "/highlighting/highlights"}; + ChatterinoSetting> blacklistSetting = {"/highlighting/blacklist"}; }; } // namespace chatterino diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index 03be79160..4c1997cfe 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -440,9 +440,6 @@ void TwitchMessageBuilder::parseHighlights() currentPlayerUrl = highlightSoundUrl; } - QStringList blackList = - app->settings->highlightUserBlacklist.getValue().split("\n", QString::SkipEmptyParts); - // TODO: This vector should only be rebuilt upon highlights being changed // fourtf: should be implemented in the HighlightsController std::vector activeHighlights = app->highlights->phrases.getVector(); @@ -459,7 +456,7 @@ void TwitchMessageBuilder::parseHighlights() bool hasFocus = (QApplication::focusWidget() != nullptr); - if (!blackList.contains(this->ircMessage->nick(), Qt::CaseInsensitive)) { + if (!app->highlights->blacklistContains(this->ircMessage->nick())) { for (const HighlightPhrase &highlight : activeHighlights) { if (highlight.isMatch(this->originalMessage)) { Log("Highlight because {} matches {}", this->originalMessage, diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index 315e8b60d..cae03d803 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -102,7 +102,6 @@ public: QStringSetting pathHighlightSound = {"/highlighting/highlightSoundPath", "qrc:/sounds/ping2.wav"}; - QStringSetting highlightUserBlacklist = {"/highlighting/blacklistedUsers", ""}; BoolSetting highlightAlwaysPlaySound = {"/highlighting/alwaysPlaySound", false}; diff --git a/src/widgets/settingspages/HighlightingPage.cpp b/src/widgets/settingspages/HighlightingPage.cpp index 03002a3e5..7c8886531 100644 --- a/src/widgets/settingspages/HighlightingPage.cpp +++ b/src/widgets/settingspages/HighlightingPage.cpp @@ -1,6 +1,7 @@ #include "HighlightingPage.hpp" #include "Application.hpp" +#include "controllers/highlights/HighlightBlacklistModel.hpp" #include "controllers/highlights/HighlightController.hpp" #include "controllers/highlights/HighlightModel.hpp" #include "debug/Log.hpp" @@ -63,22 +64,28 @@ HighlightingPage::HighlightingPage() HighlightPhrase{"my phrase", true, false, false}); }); } + auto disabledUsers = tabs.appendTab(new QVBoxLayout, "Disabled Users"); { - auto text = disabledUsers.emplace().getElement(); + EditableModelView *view = + disabledUsers + .emplace(app->highlights->createBlacklistModel(nullptr)) + .getElement(); - QObject::connect(text, &QTextEdit::textChanged, this, - [this] { this->disabledUsersChangedTimer.start(200); }); + view->setTitles({"Pattern", "Regex"}); + view->getTableView()->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed); + view->getTableView()->horizontalHeader()->setSectionResizeMode( + 0, QHeaderView::Stretch); - QObject::connect( - &this->disabledUsersChangedTimer, &QTimer::timeout, this, [text, app]() { - QStringList list = text->toPlainText().split("\n", QString::SkipEmptyParts); - list.removeDuplicates(); - app->settings->highlightUserBlacklist = list.join("\n") + "\n"; - }); + // fourtf: make class extrend BaseWidget and add this to dpiChanged + QTimer::singleShot(1, [view] { + view->getTableView()->resizeColumnsToContents(); + view->getTableView()->setColumnWidth(0, 200); + }); - app->settings->highlightUserBlacklist.connect([=](const QString &str, auto) { - text->setPlainText(str); // + view->addButtonPressed.connect([] { + getApp()->highlights->blacklistedUsers.appendItem( + HighlightBlacklistUser{"blacklisted user", false}); }); } }