diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d789fb2f..d76c5724b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,11 @@ - Minor: Remove TwitchEmotes.com attribution and the open/copy options when right-clicking a Twitch Emote. (#2214, #3136) - Minor: Strip leading @ and trailing , from username in /user and /usercard commands. (#3143) - Minor: Display a system message when reloading subscription emotes to match BTTV/FFZ behavior (#3135) +- Minor: Allow resub messages to show in `/mentions` tab (#3148) - Minor: Added a setting to hide similar messages by any user. (#2716) - Minor: Duplicate spaces now count towards the display message length. (#3002) +- Minor: Commands are now backed up. (#3168) +- Bugfix: Restored ability to send duplicate `/me` messages. (#3166) - Bugfix: Notifications for moderators about other moderators deleting messages can now be disabled. (#3121) - Bugfix: Moderation mode and active filters are now preserved when opening a split as a popup. (#3113, #3130) - Bugfix: Fixed a bug that caused all badge highlights to use the same color. (#3132, #3134) diff --git a/src/controllers/commands/CommandController.cpp b/src/controllers/commands/CommandController.cpp index 8e977af9b..13368917e 100644 --- a/src/controllers/commands/CommandController.cpp +++ b/src/controllers/commands/CommandController.cpp @@ -267,6 +267,8 @@ void CommandController::initialize(Settings &, Paths &paths) auto path = combinePath(paths.settingsDirectory, "commands.json"); this->sm_ = std::make_shared(); this->sm_->setPath(path.toStdString()); + this->sm_->setBackupEnabled(true); + this->sm_->setBackupSlots(9); // Delayed initialization of the setting storing all commands this->commandsSetting_.reset( diff --git a/src/messages/SharedMessageBuilder.cpp b/src/messages/SharedMessageBuilder.cpp index cdc8e59f0..3af860f9f 100644 --- a/src/messages/SharedMessageBuilder.cpp +++ b/src/messages/SharedMessageBuilder.cpp @@ -157,10 +157,6 @@ void SharedMessageBuilder::parseHighlights() this->message().flags.set(MessageFlag::Highlighted); this->message().highlightColor = ColorProvider::instance().color(ColorType::Subscription); - - // This message was a subscription. - // Don't check for any other highlight phrases. - return; } // XXX: Non-common term in SharedMessageBuilder @@ -220,7 +216,10 @@ void SharedMessageBuilder::parseHighlights() << "sent a message"; this->message().flags.set(MessageFlag::Highlighted); - this->message().highlightColor = userHighlight.getColor(); + if (!this->message().flags.has(MessageFlag::Subscription)) + { + this->message().highlightColor = userHighlight.getColor(); + } if (userHighlight.showInMentions()) { @@ -289,7 +288,10 @@ void SharedMessageBuilder::parseHighlights() } this->message().flags.set(MessageFlag::Highlighted); - this->message().highlightColor = highlight.getColor(); + if (!this->message().flags.has(MessageFlag::Subscription)) + { + this->message().highlightColor = highlight.getColor(); + } if (highlight.showInMentions()) { @@ -344,7 +346,11 @@ void SharedMessageBuilder::parseHighlights() if (!badgeHighlightSet) { this->message().flags.set(MessageFlag::Highlighted); - this->message().highlightColor = highlight.getColor(); + if (!this->message().flags.has(MessageFlag::Subscription)) + { + this->message().highlightColor = highlight.getColor(); + } + badgeHighlightSet = true; } diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp index 0e720d0ee..a4ce3c513 100644 --- a/src/providers/twitch/IrcMessageHandler.cpp +++ b/src/providers/twitch/IrcMessageHandler.cpp @@ -313,12 +313,9 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message, const auto highlighted = msg->flags.has(MessageFlag::Highlighted); const auto showInMentions = msg->flags.has(MessageFlag::ShowInMentions); - if (!isSub) + if (highlighted && showInMentions) { - if (highlighted && showInMentions) - { - server.mentionsChannel->addMessage(msg); - } + server.mentionsChannel->addMessage(msg); } chan->addMessage(msg); diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index 94c458a55..db35de7b2 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -377,6 +377,16 @@ void TwitchChannel::sendMessage(const QString &message) if (parsedMessage == this->lastSentMessage_) { auto spaceIndex = parsedMessage.indexOf(' '); + // If the message starts with either '/' or '.' Twitch will treat it as a command, omitting + // first space and only rest of the arguments treated as actual message content + // In cases when user sends a message like ". .a b" first character and first space are omitted as well + bool ignoreFirstSpace = + parsedMessage.at(0) == '/' || parsedMessage.at(0) == '.'; + if (ignoreFirstSpace) + { + spaceIndex = parsedMessage.indexOf(' ', spaceIndex + 1); + } + if (spaceIndex == -1) { // no spaces found, fall back to old magic character diff --git a/src/util/StreamLink.cpp b/src/util/StreamLink.cpp index 2ac42cfa6..b631cad51 100644 --- a/src/util/StreamLink.cpp +++ b/src/util/StreamLink.cpp @@ -200,7 +200,7 @@ void openStreamlinkForChannel(const QString &channel) if (preferredQuality == "choose") { getStreamQualities(channelURL, [=](QStringList qualityOptions) { - QualityPopup::showDialog(channel, qualityOptions); + QualityPopup::showDialog(channelURL, qualityOptions); }); return; diff --git a/src/widgets/BasePopup.cpp b/src/widgets/BasePopup.cpp index 0f3ed3c1d..afae740b6 100644 --- a/src/widgets/BasePopup.cpp +++ b/src/widgets/BasePopup.cpp @@ -1,5 +1,7 @@ #include "widgets/BasePopup.hpp" +#include +#include #include namespace chatterino { @@ -20,4 +22,66 @@ void BasePopup::keyPressEvent(QKeyEvent *e) BaseWindow::keyPressEvent(e); } +bool BasePopup::handleEscape(QKeyEvent *e, QDialogButtonBox *buttonBox) +{ + assert(buttonBox != nullptr); + + if (e->key() == Qt::Key_Escape) + { + auto buttons = buttonBox->buttons(); + for (auto *button : buttons) + { + if (auto role = buttonBox->buttonRole(button); + role == QDialogButtonBox::ButtonRole::RejectRole) + { + button->click(); + return true; + } + } + } + + return false; +} + +bool BasePopup::handleEnter(QKeyEvent *e, QDialogButtonBox *buttonBox) +{ + assert(buttonBox != nullptr); + + if (!e->modifiers() || + (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) + { + switch (e->key()) + { + case Qt::Key_Enter: + case Qt::Key_Return: { + auto buttons = buttonBox->buttons(); + QAbstractButton *acceptButton = nullptr; + for (auto *button : buttons) + { + if (button->hasFocus()) + { + button->click(); + return true; + } + + if (auto role = buttonBox->buttonRole(button); + role == QDialogButtonBox::ButtonRole::AcceptRole) + { + acceptButton = button; + } + } + + if (acceptButton != nullptr) + { + acceptButton->click(); + return true; + } + } + break; + } + } + + return false; +} + } // namespace chatterino diff --git a/src/widgets/BasePopup.hpp b/src/widgets/BasePopup.hpp index 5741b0006..d7942f2fc 100644 --- a/src/widgets/BasePopup.hpp +++ b/src/widgets/BasePopup.hpp @@ -3,6 +3,8 @@ #include "common/FlagsEnum.hpp" #include "widgets/BaseWindow.hpp" +class QDialogButtonBox; + namespace chatterino { class BasePopup : public BaseWindow @@ -13,6 +15,12 @@ public: protected: void keyPressEvent(QKeyEvent *e) override; + + // handleEscape is a helper function for clicking the "Reject" role button of a button box when the Escape button is pressed + bool handleEscape(QKeyEvent *e, QDialogButtonBox *buttonBox); + + // handleEnter is a helper function for clicking the "Accept" role button of a button box when Return or Enter is pressed + bool handleEnter(QKeyEvent *e, QDialogButtonBox *buttonBox); }; } // namespace chatterino diff --git a/src/widgets/dialogs/QualityPopup.cpp b/src/widgets/dialogs/QualityPopup.cpp index 414ab2548..52f7dba47 100644 --- a/src/widgets/dialogs/QualityPopup.cpp +++ b/src/widgets/dialogs/QualityPopup.cpp @@ -7,35 +7,32 @@ namespace chatterino { -QualityPopup::QualityPopup(const QString &_channelName, QStringList options) +QualityPopup::QualityPopup(const QString &channelURL, QStringList options) : BasePopup({}, static_cast(&(getApp()->windows->getMainWindow()))) - , channelName_(_channelName) + , channelURL_(channelURL) { - this->ui_.okButton.setText("OK"); - this->ui_.cancelButton.setText("Cancel"); + this->ui_.selector = new QComboBox(this); + this->ui_.vbox = new QVBoxLayout(this); + this->ui_.buttonBox = new QDialogButtonBox( + QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - QObject::connect(&this->ui_.okButton, &QPushButton::clicked, this, + QObject::connect(this->ui_.buttonBox, &QDialogButtonBox::accepted, this, &QualityPopup::okButtonClicked); - QObject::connect(&this->ui_.cancelButton, &QPushButton::clicked, this, + QObject::connect(this->ui_.buttonBox, &QDialogButtonBox::rejected, this, &QualityPopup::cancelButtonClicked); - this->ui_.buttonBox.addButton(&this->ui_.okButton, - QDialogButtonBox::ButtonRole::AcceptRole); - this->ui_.buttonBox.addButton(&this->ui_.cancelButton, - QDialogButtonBox::ButtonRole::RejectRole); + this->ui_.selector->addItems(options); - this->ui_.selector.addItems(options); + this->ui_.vbox->addWidget(this->ui_.selector); + this->ui_.vbox->addWidget(this->ui_.buttonBox); - this->ui_.vbox.addWidget(&this->ui_.selector); - this->ui_.vbox.addWidget(&this->ui_.buttonBox); - - this->setLayout(&this->ui_.vbox); + this->setLayout(this->ui_.vbox); } -void QualityPopup::showDialog(const QString &channelName, QStringList options) +void QualityPopup::showDialog(const QString &channelURL, QStringList options) { - QualityPopup *instance = new QualityPopup(channelName, options); + QualityPopup *instance = new QualityPopup(channelURL, options); instance->window()->setWindowTitle("Chatterino - select stream quality"); instance->setAttribute(Qt::WA_DeleteOnClose, true); @@ -43,16 +40,27 @@ void QualityPopup::showDialog(const QString &channelName, QStringList options) instance->show(); instance->activateWindow(); instance->raise(); - instance->setFocus(); +} + +void QualityPopup::keyPressEvent(QKeyEvent *e) +{ + if (this->handleEscape(e, this->ui_.buttonBox)) + { + return; + } + if (this->handleEnter(e, this->ui_.buttonBox)) + { + return; + } + + BasePopup::keyPressEvent(e); } void QualityPopup::okButtonClicked() { - QString channelURL = "twitch.tv/" + this->channelName_; - try { - openStreamlink(channelURL, this->ui_.selector.currentText()); + openStreamlink(this->channelURL_, this->ui_.selector->currentText()); } catch (const Exception &ex) { diff --git a/src/widgets/dialogs/QualityPopup.hpp b/src/widgets/dialogs/QualityPopup.hpp index cb5f06205..f0820218d 100644 --- a/src/widgets/dialogs/QualityPopup.hpp +++ b/src/widgets/dialogs/QualityPopup.hpp @@ -4,7 +4,6 @@ #include #include -#include #include namespace chatterino { @@ -12,22 +11,23 @@ namespace chatterino { class QualityPopup : public BasePopup { public: - QualityPopup(const QString &_channelName, QStringList options); - static void showDialog(const QString &_channelName, QStringList options); + QualityPopup(const QString &channelURL, QStringList options); + static void showDialog(const QString &channelURL, QStringList options); + +protected: + void keyPressEvent(QKeyEvent *e) override; private: void okButtonClicked(); void cancelButtonClicked(); struct { - QVBoxLayout vbox; - QComboBox selector; - QDialogButtonBox buttonBox; - QPushButton okButton; - QPushButton cancelButton; + QVBoxLayout *vbox; + QComboBox *selector; + QDialogButtonBox *buttonBox; } ui_; - QString channelName_; + QString channelURL_; }; } // namespace chatterino