diff --git a/chatterino.pro b/chatterino.pro index b45c964a7..980bb2c52 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -154,7 +154,6 @@ SOURCES += \ src/widgets/settingspages/commandpage.cpp \ src/widgets/settingspages/emotespage.cpp \ src/widgets/settingspages/highlightingpage.cpp \ - src/widgets/settingspages/ignoremessagespage.cpp \ src/widgets/settingspages/ignoreuserspage.cpp \ src/widgets/settingspages/keyboardsettingspage.cpp \ src/widgets/settingspages/logspage.cpp \ @@ -192,6 +191,8 @@ SOURCES += \ src/controllers/commands/commandcontroller.cpp \ src/controllers/highlights/highlightcontroller.cpp \ src/controllers/highlights/highlightmodel.cpp \ + src/controllers/ignores/ignorecontroller.cpp \ + src/controllers/ignores/ignoremodel.cpp \ src/widgets/helper/editablemodelview.cpp \ src/controllers/accounts/accountcontroller.cpp \ src/controllers/accounts/accountmodel.cpp \ @@ -289,7 +290,6 @@ HEADERS += \ src/widgets/settingspages/commandpage.hpp \ src/widgets/settingspages/emotespage.hpp \ src/widgets/settingspages/highlightingpage.hpp \ - src/widgets/settingspages/ignoremessagespage.hpp \ src/widgets/settingspages/ignoreuserspage.hpp \ src/widgets/settingspages/keyboardsettingspage.hpp \ src/widgets/settingspages/logspage.hpp \ @@ -335,6 +335,9 @@ HEADERS += \ src/controllers/highlights/highlightcontroller.hpp \ src/controllers/highlights/highlightphrase.hpp \ src/controllers/highlights/highlightmodel.hpp \ + src/controllers/ignores/ignorecontroller.hpp \ + src/controllers/ignores/ignorephrase.hpp \ + src/controllers/ignores/ignoremodel.hpp \ src/widgets/helper/editablemodelview.hpp \ src/controllers/accounts/accountcontroller.hpp \ src/controllers/accounts/accountmodel.hpp \ diff --git a/src/application.cpp b/src/application.cpp index 501669e91..53746ff23 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -2,6 +2,7 @@ #include "controllers/commands/commandcontroller.hpp" #include "controllers/highlights/highlightcontroller.hpp" +#include "controllers/ignores/ignorecontroller.hpp" #include "providers/twitch/pubsub.hpp" #include "providers/twitch/twitchserver.hpp" #include "singletons/accountmanager.hpp" @@ -66,6 +67,7 @@ void Application::construct() this->logging = new singletons::LoggingManager; this->commands = new controllers::commands::CommandController; this->highlights = new controllers::highlights::HighlightController; + this->ignores = new controllers::ignores::IgnoreController; this->accounts = new singletons::AccountManager; this->emotes = new singletons::EmoteManager; this->settings = new singletons::SettingManager; @@ -100,6 +102,7 @@ void Application::initialize() this->resources->initialize(); this->highlights->initialize(); + this->ignores->initialize(); this->emotes->initialize(); diff --git a/src/application.hpp b/src/application.hpp index 2f23a50a0..ed13a808c 100644 --- a/src/application.hpp +++ b/src/application.hpp @@ -23,7 +23,10 @@ class CommandController; namespace highlights { class HighlightController; } +namespace ignores { +class IgnoreController; } +} // namespace controllers namespace singletons { @@ -63,6 +66,7 @@ public: singletons::LoggingManager *logging = nullptr; controllers::commands::CommandController *commands = nullptr; controllers::highlights::HighlightController *highlights = nullptr; + controllers::ignores::IgnoreController *ignores = nullptr; singletons::AccountManager *accounts = nullptr; singletons::EmoteManager *emotes = nullptr; singletons::NativeMessagingManager *nativeMessaging = nullptr; diff --git a/src/controllers/highlights/highlightphrase.hpp b/src/controllers/highlights/highlightphrase.hpp index 2d7161b16..7346295d3 100644 --- a/src/controllers/highlights/highlightphrase.hpp +++ b/src/controllers/highlights/highlightphrase.hpp @@ -1,5 +1,6 @@ #pragma once +#include "util/rapidjson-helpers.hpp" #include "util/serialize-custom.hpp" #include @@ -101,36 +102,14 @@ struct Deserialize { } QString _pattern; - if (value.HasMember("pattern")) { - const rapidjson::Value &key = value["pattern"]; - if (key.IsString()) { - _pattern = key.GetString(); - } - } - bool _alert = true; - if (value.HasMember("alert")) { - const rapidjson::Value &alert = value["alert"]; - if (alert.IsBool()) { - _alert = alert.GetBool(); - } - } - bool _sound = false; - if (value.HasMember("sound")) { - const rapidjson::Value &sound = value["sound"]; - if (sound.IsBool()) { - _sound = sound.GetBool(); - } - } - bool _isRegex = false; - if (value.HasMember("regex")) { - const rapidjson::Value ®ex = value["regex"]; - if (regex.IsBool()) { - _isRegex = regex.GetBool(); - } - } + + chatterino::rj::getSafe(value, "pattern", _pattern); + chatterino::rj::getSafe(value, "alert", _alert); + chatterino::rj::getSafe(value, "sound", _sound); + chatterino::rj::getSafe(value, "regex", _isRegex); return chatterino::controllers::highlights::HighlightPhrase(_pattern, _alert, _sound, _isRegex); diff --git a/src/controllers/ignores/ignorecontroller.cpp b/src/controllers/ignores/ignorecontroller.cpp new file mode 100644 index 000000000..bd1910ca8 --- /dev/null +++ b/src/controllers/ignores/ignorecontroller.cpp @@ -0,0 +1,36 @@ +#include "controllers/ignores/ignorecontroller.hpp" + +#include "application.hpp" +#include "controllers/ignores/ignoremodel.hpp" + +#include + +namespace chatterino { +namespace controllers { +namespace ignores { + +void IgnoreController::initialize() +{ + assert(!this->initialized); + this->initialized = true; + + for (const IgnorePhrase &phrase : this->ignoresSetting.getValue()) { + this->phrases.appendItem(phrase); + } + + this->phrases.delayedItemsChanged.connect([this] { // + this->ignoresSetting.setValue(this->phrases.getVector()); + }); +} + +IgnoreModel *IgnoreController::createModel(QObject *parent) +{ + IgnoreModel *model = new IgnoreModel(parent); + model->init(&this->phrases); + + return model; +} + +} // namespace ignores +} // namespace controllers +} // namespace chatterino diff --git a/src/controllers/ignores/ignorecontroller.hpp b/src/controllers/ignores/ignorecontroller.hpp new file mode 100644 index 000000000..84c3e27fe --- /dev/null +++ b/src/controllers/ignores/ignorecontroller.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "controllers/ignores/ignorephrase.hpp" +#include "singletons/settingsmanager.hpp" +#include "util/signalvector2.hpp" + +namespace chatterino { +namespace controllers { +namespace ignores { + +class IgnoreModel; + +class IgnoreController +{ +public: + void initialize(); + + util::UnsortedSignalVector phrases; + + IgnoreModel *createModel(QObject *parent); + +private: + bool initialized = false; + + singletons::ChatterinoSetting> ignoresSetting = { + "/ignore/phrases"}; +}; + +} // namespace ignores +} // namespace controllers +} // namespace chatterino diff --git a/src/controllers/ignores/ignoremodel.cpp b/src/controllers/ignores/ignoremodel.cpp new file mode 100644 index 000000000..d8e59dc2c --- /dev/null +++ b/src/controllers/ignores/ignoremodel.cpp @@ -0,0 +1,35 @@ +#include "ignoremodel.hpp" + +#include "application.hpp" +#include "singletons/settingsmanager.hpp" +#include "util/standarditemhelper.hpp" + +namespace chatterino { +namespace controllers { +namespace ignores { + +// commandmodel +IgnoreModel::IgnoreModel(QObject *parent) + : util::SignalVectorModel(2, parent) +{ +} + +// turn a vector item into a model row +IgnorePhrase IgnoreModel::getItemFromRow(std::vector &row) +{ + // key, regex + + return IgnorePhrase{row[0]->data(Qt::DisplayRole).toString(), + row[1]->data(Qt::CheckStateRole).toBool()}; +} + +// turns a row in the model into a vector item +void IgnoreModel::getRowFromItem(const IgnorePhrase &item, std::vector &row) +{ + util::setStringItem(row[0], item.getPattern()); + util::setBoolItem(row[1], item.isRegex()); +} + +} // namespace ignores +} // namespace controllers +} // namespace chatterino diff --git a/src/controllers/ignores/ignoremodel.hpp b/src/controllers/ignores/ignoremodel.hpp new file mode 100644 index 000000000..a0bba4ec8 --- /dev/null +++ b/src/controllers/ignores/ignoremodel.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include "controllers/ignores/ignorephrase.hpp" +#include "util/signalvectormodel.hpp" + +namespace chatterino { +namespace controllers { +namespace ignores { + +class IgnoreController; + +class IgnoreModel : public util::SignalVectorModel +{ + explicit IgnoreModel(QObject *parent); + +protected: + // turn a vector item into a model row + virtual IgnorePhrase getItemFromRow(std::vector &row) override; + + // turns a row in the model into a vector item + virtual void getRowFromItem(const IgnorePhrase &item, + std::vector &row) override; + + friend class IgnoreController; +}; + +} // namespace ignores +} // namespace controllers +} // namespace chatterino diff --git a/src/controllers/ignores/ignorephrase.hpp b/src/controllers/ignores/ignorephrase.hpp new file mode 100644 index 000000000..4fdef9ffa --- /dev/null +++ b/src/controllers/ignores/ignorephrase.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include "util/rapidjson-helpers.hpp" +#include "util/serialize-custom.hpp" + +#include +#include +#include + +#include + +namespace chatterino { +namespace controllers { +namespace ignores { + +class IgnorePhrase +{ + QString pattern; + bool _isRegex; + QRegularExpression regex; + +public: + bool operator==(const IgnorePhrase &other) const + { + return std::tie(this->pattern, this->_isRegex) == std::tie(other.pattern, other._isRegex); + } + + IgnorePhrase(const QString &_pattern, bool isRegex) + : pattern(_pattern) + , _isRegex(isRegex) + , regex(_isRegex ? _pattern : "\\b" + QRegularExpression::escape(_pattern) + "\\b", + QRegularExpression::CaseInsensitiveOption) + { + } + + const QString &getPattern() const + { + return this->pattern; + } + 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(); + } +}; +} // namespace ignores +} // namespace controllers +} // namespace chatterino + +namespace pajlada { +namespace Settings { + +template <> +struct Serialize { + static rapidjson::Value get(const chatterino::controllers::ignores::IgnorePhrase &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::controllers::ignores::IgnorePhrase get(const rapidjson::Value &value) + { + if (!value.IsObject()) { + return chatterino::controllers::ignores::IgnorePhrase(QString(), false); + } + + QString _pattern; + bool _isRegex = false; + + chatterino::rj::getSafe(value, "pattern", _pattern); + chatterino::rj::getSafe(value, "regex", _isRegex); + + return chatterino::controllers::ignores::IgnorePhrase(_pattern, _isRegex); + } +}; + +} // namespace Settings +} // namespace pajlada diff --git a/src/providers/twitch/twitchmessagebuilder.cpp b/src/providers/twitch/twitchmessagebuilder.cpp index 1926f7620..172cc3256 100644 --- a/src/providers/twitch/twitchmessagebuilder.cpp +++ b/src/providers/twitch/twitchmessagebuilder.cpp @@ -2,6 +2,7 @@ #include "application.hpp" #include "controllers/highlights/highlightcontroller.hpp" +#include "controllers/ignores/ignorecontroller.hpp" #include "debug/log.hpp" #include "providers/twitch/twitchchannel.hpp" #include "singletons/accountmanager.hpp" @@ -54,10 +55,12 @@ TwitchMessageBuilder::TwitchMessageBuilder(Channel *_channel, bool TwitchMessageBuilder::isIgnored() const { auto app = getApp(); - std::shared_ptr> ignoredKeywords = app->settings->getIgnoredKeywords(); - for (const QString &keyword : *ignoredKeywords) { - if (this->originalMessage.contains(keyword, Qt::CaseInsensitive)) { + // TODO(pajlada): Do we need to check if the phrase is valid first? + for (const auto &phrase : app->ignores->phrases.getVector()) { + if (phrase.isMatch(this->originalMessage)) { + debug::Log("Blocking message because it contains ignored phrase {}", + phrase.getPattern()); return true; } } diff --git a/src/singletons/settingsmanager.cpp b/src/singletons/settingsmanager.cpp index 2d70b769d..2516df09a 100644 --- a/src/singletons/settingsmanager.cpp +++ b/src/singletons/settingsmanager.cpp @@ -19,7 +19,6 @@ void _actuallyRegisterSetting(std::weak_ptr set } SettingManager::SettingManager() - : _ignoredKeywords(new std::vector) { qDebug() << "init SettingManager"; @@ -37,7 +36,6 @@ SettingManager::SettingManager() void SettingManager::initialize() { this->moderationActions.connect([this](auto, auto) { this->updateModerationActions(); }); - this->ignoredKeywords.connect([this](auto, auto) { this->updateIgnoredKeywords(); }); this->timestampFormat.connect([](auto, auto) { auto app = getApp(); @@ -151,11 +149,6 @@ std::vector SettingManager::getModerationActions() const return this->_moderationActions; } -const std::shared_ptr> SettingManager::getIgnoredKeywords() const -{ - return this->_ignoredKeywords; -} - void SettingManager::updateModerationActions() { auto app = getApp(); @@ -224,21 +217,5 @@ void SettingManager::updateModerationActions() } } -void SettingManager::updateIgnoredKeywords() -{ - static QRegularExpression newLineRegex("(\r\n?|\n)+"); - - auto items = new std::vector(); - - for (const QString &line : this->ignoredKeywords.getValue().split(newLineRegex)) { - QString line2 = line.trimmed(); - - if (!line2.isEmpty()) { - items->push_back(line2); - } - } - - this->_ignoredKeywords = std::shared_ptr>(items); -} } // namespace singletons } // namespace chatterino diff --git a/src/singletons/settingsmanager.hpp b/src/singletons/settingsmanager.hpp index 196f13d06..f9b2fcbdd 100644 --- a/src/singletons/settingsmanager.hpp +++ b/src/singletons/settingsmanager.hpp @@ -86,7 +86,6 @@ public: /// Ingored Users BoolSetting enableTwitchIgnoredUsers = {"/ignore/enableTwitchIgnoredUsers", true}; - QStringSetting ignoredKeywords = {"/ignore/ignoredKeywords", ""}; /// Moderation QStringSetting moderationActions = {"/moderation/actions", "/ban {user}\n/timeout {user} 300"}; @@ -123,16 +122,13 @@ public: void recallSnapshot(); std::vector getModerationActions() const; - const std::shared_ptr> getIgnoredKeywords() const; pajlada::Signals::NoArgSignal wordFlagsChanged; private: std::vector _moderationActions; std::unique_ptr snapshot; - std::shared_ptr> _ignoredKeywords; void updateModerationActions(); - void updateIgnoredKeywords(); messages::MessageElement::Flags wordFlags = messages::MessageElement::Default; diff --git a/src/widgets/helper/splitinput.cpp b/src/widgets/helper/splitinput.cpp index a549a38f5..7d595acdd 100644 --- a/src/widgets/helper/splitinput.cpp +++ b/src/widgets/helper/splitinput.cpp @@ -159,6 +159,9 @@ void SplitInput::installKeyPressedEvent() } this->prevIndex = this->prevMsg.size(); } else if (event->key() == Qt::Key_Up) { + if ((event->modifiers() & Qt::ShiftModifier) != 0) { + return; + } if (event->modifiers() == Qt::AltModifier) { SplitContainer *page = static_cast(this->chatWidget->parentWidget()); @@ -184,6 +187,9 @@ void SplitInput::installKeyPressedEvent() } } } else if (event->key() == Qt::Key_Down) { + if ((event->modifiers() & Qt::ShiftModifier) != 0) { + return; + } if (event->modifiers() == Qt::AltModifier) { SplitContainer *page = static_cast(this->chatWidget->parentWidget()); diff --git a/src/widgets/settingsdialog.cpp b/src/widgets/settingsdialog.cpp index de603045c..a81eb9d8d 100644 --- a/src/widgets/settingsdialog.cpp +++ b/src/widgets/settingsdialog.cpp @@ -11,7 +11,6 @@ #include "widgets/settingspages/emotespage.hpp" #include "widgets/settingspages/externaltoolspage.hpp" #include "widgets/settingspages/highlightingpage.hpp" -#include "widgets/settingspages/ignoremessagespage.hpp" #include "widgets/settingspages/ignoreuserspage.hpp" #include "widgets/settingspages/keyboardsettingspage.hpp" #include "widgets/settingspages/logspage.hpp" @@ -89,7 +88,6 @@ void SettingsDialog::addTabs() this->addTab(new settingspages::CommandPage); // this->addTab(new settingspages::EmotesPage); this->addTab(new settingspages::HighlightingPage); - // this->addTab(new settingspages::IgnoreMessagesPage); this->addTab(new settingspages::IgnoreUsersPage); this->ui.tabContainer->addSpacing(16); diff --git a/src/widgets/settingspages/ignoremessagespage.cpp b/src/widgets/settingspages/ignoremessagespage.cpp deleted file mode 100644 index 714ef3351..000000000 --- a/src/widgets/settingspages/ignoremessagespage.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "ignoremessagespage.hpp" - -#include "application.hpp" -#include "util/layoutcreator.hpp" - -#include -#include - -namespace chatterino { -namespace widgets { -namespace settingspages { - -IgnoreMessagesPage::IgnoreMessagesPage() - : SettingsPage("Ignore Messages", "") -{ - auto app = getApp(); - util::LayoutCreator layoutCreator(this); - auto layout = layoutCreator.setLayoutType(); - - layout.emplace("Ignored keywords:"); - QTextEdit *textEdit = layout.emplace().getElement(); - - textEdit->setPlainText(app->settings->ignoredKeywords); - - QObject::connect(textEdit, &QTextEdit::textChanged, - [this] { this->keywordsUpdated.start(200); }); - - QObject::connect(&this->keywordsUpdated, &QTimer::timeout, [textEdit, app] { - QString text = textEdit->toPlainText(); - - app->settings->ignoredKeywords = text; - }); - - // ---- misc - this->keywordsUpdated.setSingleShot(true); -} - -} // namespace settingspages -} // namespace widgets -} // namespace chatterino diff --git a/src/widgets/settingspages/ignoremessagespage.hpp b/src/widgets/settingspages/ignoremessagespage.hpp deleted file mode 100644 index 571b91528..000000000 --- a/src/widgets/settingspages/ignoremessagespage.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "widgets/settingspages/settingspage.hpp" - -#include - -namespace chatterino { -namespace widgets { -namespace settingspages { - -class IgnoreMessagesPage : public SettingsPage -{ -public: - IgnoreMessagesPage(); - - QTimer keywordsUpdated; -}; - -} // namespace settingspages -} // namespace widgets -} // namespace chatterino diff --git a/src/widgets/settingspages/ignoreuserspage.cpp b/src/widgets/settingspages/ignoreuserspage.cpp index 830ad0d39..f2bb46e42 100644 --- a/src/widgets/settingspages/ignoreuserspage.cpp +++ b/src/widgets/settingspages/ignoreuserspage.cpp @@ -1,15 +1,19 @@ #include "ignoreuserspage.hpp" #include "application.hpp" +#include "controllers/ignores/ignorecontroller.hpp" +#include "controllers/ignores/ignoremodel.hpp" #include "singletons/accountmanager.hpp" #include "singletons/settingsmanager.hpp" #include "util/layoutcreator.hpp" +#include "widgets/helper/editablemodelview.hpp" #include #include #include #include #include +#include #include // clang-format off @@ -60,7 +64,22 @@ IgnoreUsersPage::IgnoreUsersPage() // messages auto messages = tabs.appendTab(new QVBoxLayout, "Messages"); { - messages.emplace("wip"); + helper::EditableModelView *view = + *messages.emplace(app->ignores->createModel(nullptr)); + view->setTitles({"Pattern", "Regex"}); + view->getTableView()->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed); + view->getTableView()->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + + // fourtf: make class extrend BaseWidget and add this to dpiChanged + QTimer::singleShot(1, [view] { + view->getTableView()->resizeColumnsToContents(); + view->getTableView()->setColumnWidth(0, 200); + }); + + view->addButtonPressed.connect([] { + getApp()->ignores->phrases.appendItem( + controllers::ignores::IgnorePhrase{"my phrase", false}); + }); } auto label = layout.emplace(INFO);