From c26fb01df50b799a607d76ee1dcd3fd62f083b34 Mon Sep 17 00:00:00 2001 From: pajlada Date: Sat, 1 Jan 2022 14:15:52 +0100 Subject: [PATCH 1/7] Fixed crash that could occur when the user closes the Settings dialog (#3444) --- CHANGELOG.md | 1 + src/widgets/helper/ChannelView.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60d4fbad099..35754983cdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ - Bugfix: Fixed zero-width emotes sometimes wrapping lines incorrectly. (#3389) - Bugfix: Fixed using special chars in Windows username breaking the storage of custom commands (#3397) - Bugfix: Fixed character counter changing fonts after going over the limit. (#3422) +- Bugfix: Fixed crash that could occur if the user opens/closes ChannelViews (e.g. EmotePopup, or Splits) then modifies the showLastMessageIndicator setting. (#3444) - Dev: Add GitHub action to test builds without precompiled headers enabled. (#3327) - Dev: Renamed CMake's build option `USE_SYSTEM_QT5KEYCHAIN` to `USE_SYSTEM_QTKEYCHAIN`. (#3103) - Dev: Add benchmarks that can be compiled with the `BUILD_BENCHMARKS` CMake flag. Off by default. (#3038) diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 1a51f118405..0f450cb149c 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -194,7 +194,7 @@ void ChannelView::initializeSignals() [this](auto, auto) { this->update(); }, - this); + this->signalHolder_); this->signalHolder_.managedConnect(getApp()->windows->gifRepaintRequested, [&] { From ea462f94e43a8490f86edd6d885b444e57f92929 Mon Sep 17 00:00:00 2001 From: Mm2PL Date: Sat, 1 Jan 2022 17:06:54 +0000 Subject: [PATCH 2/7] Check live status for all closed channels with notifications at once (#3442) Use a single api call for fetching live status of fake channels; batch by 100s Co-authored-by: Felanbird <41973452+Felanbird@users.noreply.github.com> Co-authored-by: zneix --- CHANGELOG.md | 1 + .../notifications/NotificationController.cpp | 151 +++++++++++------- .../notifications/NotificationController.hpp | 2 +- 3 files changed, 98 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35754983cdb..36a1faeaac6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,7 @@ - Bugfix: Fixed using special chars in Windows username breaking the storage of custom commands (#3397) - Bugfix: Fixed character counter changing fonts after going over the limit. (#3422) - Bugfix: Fixed crash that could occur if the user opens/closes ChannelViews (e.g. EmotePopup, or Splits) then modifies the showLastMessageIndicator setting. (#3444) +- Dev: Batch checking live status for channels with live notifications that aren't connected. (#3442) - Dev: Add GitHub action to test builds without precompiled headers enabled. (#3327) - Dev: Renamed CMake's build option `USE_SYSTEM_QT5KEYCHAIN` to `USE_SYSTEM_QTKEYCHAIN`. (#3103) - Dev: Add benchmarks that can be compiled with the `BUILD_BENCHMARKS` CMake flag. Off by default. (#3038) diff --git a/src/controllers/notifications/NotificationController.cpp b/src/controllers/notifications/NotificationController.cpp index ffd61fce71c..9f8bb88e1b1 100644 --- a/src/controllers/notifications/NotificationController.cpp +++ b/src/controllers/notifications/NotificationController.cpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace chatterino { @@ -127,8 +128,38 @@ NotificationModel *NotificationController::createModel(QObject *parent, return model; } +namespace { + // TODO: combine this with getEmoteSetBatches in TwitchAccount.cpp, maybe some templated thing + std::vector getChannelsInBatches(QStringList channels) + { + constexpr int batchSize = 100; + + int batchCount = (channels.size() / batchSize) + 1; + + std::vector batches; + batches.reserve(batchCount); + + for (int i = 0; i < batchCount; i++) + { + QStringList batch; + + // I hate you, msvc + int last = (std::min)(batchSize, channels.size() - batchSize * i); + for (int j = 0; j < last; j++) + { + batch.push_back(channels.at(j + (batchSize * i))); + } + batches.emplace_back(batch); + } + + return batches; + } +} // namespace + void NotificationController::fetchFakeChannels() { + qCDebug(chatterinoNotification) << "fetching fake channels"; + QStringList channels; for (std::vector::size_type i = 0; i != channelMap[Platform::Twitch].raw().size(); i++) { @@ -136,69 +167,79 @@ void NotificationController::fetchFakeChannels() channelMap[Platform::Twitch].raw()[i]); if (chan->isEmpty()) { - getFakeTwitchChannelLiveStatus( - channelMap[Platform::Twitch].raw()[i]); + channels.push_back(channelMap[Platform::Twitch].raw()[i]); } } + for (const auto &batch : getChannelsInBatches(channels)) + { + getHelix()->fetchStreams( + QStringList(), batch, + [batch, this](std::vector streams) { + std::unordered_set liveStreams; + for (const auto &stream : streams) + { + liveStreams.insert(stream.userName); + } + + for (const auto &name : batch) + { + auto it = liveStreams.find(name); + this->checkStream(it != liveStreams.end(), name); + } + }, + [batch]() { + // we done fucked up. + qCWarning(chatterinoNotification) + << "Failed to fetch live status for " << batch; + }); + } } - -void NotificationController::getFakeTwitchChannelLiveStatus( - const QString &channelName) +void NotificationController::checkStream(bool live, QString channelName) { - getHelix()->getStreamByName( - channelName, - [channelName, this](bool live, const auto &stream) { - qCDebug(chatterinoNotification) << "[TwitchChannel" << channelName - << "] Refreshing live status"; + qCDebug(chatterinoNotification) + << "[TwitchChannel" << channelName << "] Refreshing live status"; - if (!live) - { - // Stream is offline - this->removeFakeChannel(channelName); - return; - } + if (!live) + { + // Stream is offline + this->removeFakeChannel(channelName); + return; + } - // Stream is online - auto i = std::find(fakeTwitchChannels.begin(), - fakeTwitchChannels.end(), channelName); + // Stream is online + auto i = std::find(fakeTwitchChannels.begin(), fakeTwitchChannels.end(), + channelName); - if (i != fakeTwitchChannels.end()) - { - // We have already pushed the live state of this stream - // Could not find stream in fake Twitch channels! - return; - } + if (i != fakeTwitchChannels.end()) + { + // We have already pushed the live state of this stream + // Could not find stream in fake Twitch channels! + return; + } - if (Toasts::isEnabled()) - { - getApp()->toasts->sendChannelNotification(channelName, - Platform::Twitch); - } - if (getSettings()->notificationPlaySound && - !(isInStreamerMode() && - getSettings()->streamerModeSuppressLiveNotifications)) - { - getApp()->notifications->playSound(); - } - if (getSettings()->notificationFlashTaskbar && - !(isInStreamerMode() && - getSettings()->streamerModeSuppressLiveNotifications)) - { - getApp()->windows->sendAlert(); - } - MessageBuilder builder; - TwitchMessageBuilder::liveMessage(channelName, &builder); - getApp()->twitch2->liveChannel->addMessage(builder.release()); - - // Indicate that we have pushed notifications for this stream - fakeTwitchChannels.push_back(channelName); - }, - [channelName, this] { - qCDebug(chatterinoNotification) - << "[TwitchChannel" << channelName - << "] Refreshing live status (Missing ID)"; - this->removeFakeChannel(channelName); - }); + if (Toasts::isEnabled()) + { + getApp()->toasts->sendChannelNotification(channelName, + Platform::Twitch); + } + if (getSettings()->notificationPlaySound && + !(isInStreamerMode() && + getSettings()->streamerModeSuppressLiveNotifications)) + { + getApp()->notifications->playSound(); + } + if (getSettings()->notificationFlashTaskbar && + !(isInStreamerMode() && + getSettings()->streamerModeSuppressLiveNotifications)) + { + getApp()->windows->sendAlert(); + } + MessageBuilder builder; + TwitchMessageBuilder::liveMessage(channelName, &builder); + getApp()->twitch2->liveChannel->addMessage(builder.release()); + + // Indicate that we have pushed notifications for this stream + fakeTwitchChannels.push_back(channelName); } void NotificationController::removeFakeChannel(const QString channelName) diff --git a/src/controllers/notifications/NotificationController.hpp b/src/controllers/notifications/NotificationController.hpp index e8d0c4ef766..abf1e311556 100644 --- a/src/controllers/notifications/NotificationController.hpp +++ b/src/controllers/notifications/NotificationController.hpp @@ -41,7 +41,7 @@ class NotificationController final : public Singleton, private QObject void fetchFakeChannels(); void removeFakeChannel(const QString channelName); - void getFakeTwitchChannelLiveStatus(const QString &channelName); + void checkStream(bool live, QString channelName); // fakeTwitchChannels is a list of streams who are live that we have already sent out a notification for std::vector fakeTwitchChannels; From cbb6a9d5682d0c5522ab981acac85a22c8e35fe9 Mon Sep 17 00:00:00 2001 From: Infinitay Date: Sun, 2 Jan 2022 07:30:53 -0500 Subject: [PATCH 3/7] Add subscriber and founder badge highlighting (#3445) --- CHANGELOG.md | 1 + src/widgets/settingspages/HighlightingPage.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36a1faeaac6..e8d8cf31095 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ - Minor: Added autocompletion for default Twitch commands starting with the dot (e.g. `.mods` which does the same as `/mods`). (#3144) - Minor: Sorted usernames in `Users joined/parted` messages alphabetically. (#3421) - Minor: Mod list, VIP list, and Users joined/parted messages are now searchable. (#3426) +- Minor: Messages can now be highlighted by subscriber or founder badges. (#3445) - Bugfix: Fix Split Input hotkeys not being available when input is hidden (#3362) - Bugfix: Fixed colored usernames sometimes not working. (#3170) - Bugfix: Restored ability to send duplicate `/me` messages. (#3166) diff --git a/src/widgets/settingspages/HighlightingPage.cpp b/src/widgets/settingspages/HighlightingPage.cpp index 13f0948c153..a124f5ff542 100644 --- a/src/widgets/settingspages/HighlightingPage.cpp +++ b/src/widgets/settingspages/HighlightingPage.cpp @@ -38,6 +38,8 @@ namespace { {"Moderator", "moderator"}, {"Verified", "partner"}, {"VIP", "vip"}, + {"Founder", "founder"}, + {"Subscriber", "subscriber"}, {"Predicted Blue", "predictions/blue-1,predictions/blue-2"}, {"Predicted Pink", "predictions/pink-2,predictions/pink-1"}, }; From b031fc41ef7e986da642a01b189260fdb1532586 Mon Sep 17 00:00:00 2001 From: pajlada Date: Sun, 2 Jan 2022 14:09:35 +0100 Subject: [PATCH 4/7] Fix debug hotkeys not showing up in debug builds (#3447) --- src/controllers/hotkeys/ActionNames.hpp | 2 +- src/widgets/Window.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/controllers/hotkeys/ActionNames.hpp b/src/controllers/hotkeys/ActionNames.hpp index ff5d7679050..fc7f7ce702c 100644 --- a/src/controllers/hotkeys/ActionNames.hpp +++ b/src/controllers/hotkeys/ActionNames.hpp @@ -143,7 +143,7 @@ inline const std::map actionNames{ }}, {HotkeyCategory::Window, { -#ifdef C_DEBUG +#ifndef NDEBUG {"addCheerMessage", ActionDefinition{"Debug: Add cheer test message"}}, {"addEmoteMessage", ActionDefinition{"Debug: Add emote test message"}}, {"addLinkMessage", diff --git a/src/widgets/Window.cpp b/src/widgets/Window.cpp index a45391100d3..cc3dc71f255 100644 --- a/src/widgets/Window.cpp +++ b/src/widgets/Window.cpp @@ -226,6 +226,9 @@ void Window::addDebugStuff(HotkeyController::HotkeyMap &actions) // "first time chat" message miscMessages.emplace_back(R"(@badge-info=;badges=glhf-pledge/1;client-nonce=5d2627b0cbe56fa05faf5420def4807d;color=#1E90FF;display-name=oldcoeur;emote-only=1;emotes=84608:0-7;first-msg=1;flags=;id=7412fea4-8683-4cc9-a506-4228127a5c2d;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1623429859222;turbo=0;user-id=139147886;user-type= :oldcoeur!oldcoeur@oldcoeur.tmi.twitch.tv PRIVMSG #pajlada :cmonBruh)"); + // Message with founder badge + miscMessages.emplace_back(R"(@badge-info=founder/72;badges=founder/0,bits/5000;color=#FF0000;display-name=TranRed;emotes=;first-msg=0;flags=;id=7482163f-493d-41d9-b36f-fba50e0701b7;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1641123773885;turbo=0;user-id=57019243;user-type= :tranred!tranred@tranred.tmi.twitch.tv PRIVMSG #pajlada :GFMP pajaE)"); + // various link tests linkMessages.emplace_back(R"(@badge-info=subscriber/48;badges=broadcaster/1,subscriber/36,partner/1;color=#CC44FF;display-name=pajlada;emotes=;flags=;id=3c23cf3c-0864-4699-a76b-089350141147;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1577628844607;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada : Links that should pass: )" + getValidLinks().join(' ')); linkMessages.emplace_back(R"(@badge-info=subscriber/48;badges=broadcaster/1,subscriber/36,partner/1;color=#CC44FF;display-name=pajlada;emotes=;flags=;id=3c23cf3c-0864-4699-a76b-089350141147;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1577628844607;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada : Links that should NOT pass: )" + getInvalidLinks().join(' ')); From 2b9e2bd1b0553e0d3a62296f238355da8eeca873 Mon Sep 17 00:00:00 2001 From: pajlada Date: Sun, 2 Jan 2022 15:00:19 +0100 Subject: [PATCH 5/7] Notebook::select now takes the optional parameter "focusPage". (#3446) --- CHANGELOG.md | 1 + src/widgets/Notebook.cpp | 43 ++++++++++++++++-------------- src/widgets/Notebook.hpp | 12 ++++----- src/widgets/dialogs/EmotePopup.cpp | 2 +- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8d8cf31095..e581f3cd403 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,7 @@ - Dev: Renamed CMake's build option `USE_SYSTEM_QT5KEYCHAIN` to `USE_SYSTEM_QTKEYCHAIN`. (#3103) - Dev: Add benchmarks that can be compiled with the `BUILD_BENCHMARKS` CMake flag. Off by default. (#3038) - Dev: Added CMake build option `BUILD_WITH_QTKEYCHAIN` to build with or without Qt5Keychain support (On by default). (#3318) +- Dev: Notebook::select\* functions now take an optional `focusPage` parameter (true by default) which keeps the default behaviour of selecting the page after it has been selected. If set to false, the page is _not_ focused after being selected. (#3446) ## 2.3.4 diff --git a/src/widgets/Notebook.cpp b/src/widgets/Notebook.cpp index 2526ed05806..0aa5baba5a2 100644 --- a/src/widgets/Notebook.cpp +++ b/src/widgets/Notebook.cpp @@ -131,7 +131,7 @@ int Notebook::indexOf(QWidget *page) const return -1; } -void Notebook::select(QWidget *page) +void Notebook::select(QWidget *page, bool focusPage) { if (page == this->selectedPage_) { @@ -148,20 +148,23 @@ void Notebook::select(QWidget *page) item.tab->setSelected(true); item.tab->raise(); - if (item.selectedWidget == nullptr) + if (focusPage) { - item.page->setFocus(); - } - else - { - if (containsChild(page, item.selectedWidget)) + if (item.selectedWidget == nullptr) { - item.selectedWidget->setFocus(Qt::MouseFocusReason); + item.page->setFocus(); } else { - qCDebug(chatterinoWidget) - << "Notebook: selected child of page doesn't exist anymore"; + if (containsChild(page, item.selectedWidget)) + { + item.selectedWidget->setFocus(Qt::MouseFocusReason); + } + else + { + qCDebug(chatterinoWidget) << "Notebook: selected child of " + "page doesn't exist anymore"; + } } } } @@ -216,17 +219,17 @@ bool Notebook::containsChild(const QObject *obj, const QObject *child) }); } -void Notebook::selectIndex(int index) +void Notebook::selectIndex(int index, bool focusPage) { if (index < 0 || this->items_.count() <= index) { return; } - this->select(this->items_[index].page); + this->select(this->items_[index].page, focusPage); } -void Notebook::selectNextTab() +void Notebook::selectNextTab(bool focusPage) { if (this->items_.size() <= 1) { @@ -236,10 +239,10 @@ void Notebook::selectNextTab() auto index = (this->indexOf(this->selectedPage_) + 1) % this->items_.count(); - this->select(this->items_[index].page); + this->select(this->items_[index].page, focusPage); } -void Notebook::selectPreviousTab() +void Notebook::selectPreviousTab(bool focusPage) { if (this->items_.size() <= 1) { @@ -253,10 +256,10 @@ void Notebook::selectPreviousTab() index += this->items_.count(); } - this->select(this->items_[index].page); + this->select(this->items_[index].page, focusPage); } -void Notebook::selectLastTab() +void Notebook::selectLastTab(bool focusPage) { const auto size = this->items_.size(); if (size <= 1) @@ -264,7 +267,7 @@ void Notebook::selectLastTab() return; } - this->select(this->items_[size - 1].page); + this->select(this->items_[size - 1].page, focusPage); } int Notebook::getPageCount() const @@ -806,7 +809,7 @@ SplitContainer *SplitNotebook::getOrAddSelectedPage() : this->addPage(); } -void SplitNotebook::select(QWidget *page) +void SplitNotebook::select(QWidget *page, bool focusPage) { if (auto selectedPage = this->getSelectedPage()) { @@ -818,7 +821,7 @@ void SplitNotebook::select(QWidget *page) } } } - this->Notebook::select(page); + this->Notebook::select(page, focusPage); } } // namespace chatterino diff --git a/src/widgets/Notebook.hpp b/src/widgets/Notebook.hpp index 153dd533c43..9ccc5ede8e4 100644 --- a/src/widgets/Notebook.hpp +++ b/src/widgets/Notebook.hpp @@ -33,11 +33,11 @@ class Notebook : public BaseWidget void removeCurrentPage(); int indexOf(QWidget *page) const; - virtual void select(QWidget *page); - void selectIndex(int index); - void selectNextTab(); - void selectPreviousTab(); - void selectLastTab(); + virtual void select(QWidget *page, bool focusPage = true); + void selectIndex(int index, bool focusPage = true); + void selectNextTab(bool focusPage = true); + void selectPreviousTab(bool focusPage = true); + void selectLastTab(bool focusPage = true); int getPageCount() const; QWidget *getPageAt(int index) const; @@ -108,7 +108,7 @@ class SplitNotebook : public Notebook SplitContainer *addPage(bool select = false); SplitContainer *getOrAddSelectedPage(); - void select(QWidget *page) override; + void select(QWidget *page, bool focusPage = true) override; protected: void showEvent(QShowEvent *event) override; diff --git a/src/widgets/dialogs/EmotePopup.cpp b/src/widgets/dialogs/EmotePopup.cpp index e6828fc6e8b..603f257b2f0 100644 --- a/src/widgets/dialogs/EmotePopup.cpp +++ b/src/widgets/dialogs/EmotePopup.cpp @@ -203,7 +203,7 @@ void EmotePopup::addShortcuts() int result = target.toInt(&ok); if (ok) { - this->notebook_->selectIndex(result); + this->notebook_->selectIndex(result, false); } else { From 8e5468c316feac325028e72365641af9df85a673 Mon Sep 17 00:00:00 2001 From: Adam Davies <8650006+acdvs@users.noreply.github.com> Date: Sun, 2 Jan 2022 08:59:16 -0600 Subject: [PATCH 6/7] Add search to emote popup (#3404) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Co-authored-by: Rasmus Karlsson --- CHANGELOG.md | 1 + src/widgets/dialogs/EmotePopup.cpp | 225 +++++++++++++++++++++++------ src/widgets/dialogs/EmotePopup.hpp | 20 ++- 3 files changed, 204 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e581f3cd403..52002e0cbdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ - Minor: Added autocompletion for default Twitch commands starting with the dot (e.g. `.mods` which does the same as `/mods`). (#3144) - Minor: Sorted usernames in `Users joined/parted` messages alphabetically. (#3421) - Minor: Mod list, VIP list, and Users joined/parted messages are now searchable. (#3426) +- Minor: Add search to emote popup. (#3404) - Minor: Messages can now be highlighted by subscriber or founder badges. (#3445) - Bugfix: Fix Split Input hotkeys not being available when input is hidden (#3362) - Bugfix: Fixed colored usernames sometimes not working. (#3170) diff --git a/src/widgets/dialogs/EmotePopup.cpp b/src/widgets/dialogs/EmotePopup.cpp index 603f257b2f0..d9a1ccc23f6 100644 --- a/src/widgets/dialogs/EmotePopup.cpp +++ b/src/widgets/dialogs/EmotePopup.cpp @@ -16,7 +16,10 @@ #include "widgets/Scrollbar.hpp" #include "widgets/helper/ChannelView.hpp" +#include #include +#include +#include #include namespace chatterino { @@ -62,6 +65,24 @@ namespace { return builder.release(); } + auto makeEmojiMessage(EmojiMap &emojiMap) + { + MessageBuilder builder; + builder->flags.set(MessageFlag::Centered); + builder->flags.set(MessageFlag::DisableCompactEmotes); + + emojiMap.each([&builder](const auto &key, const auto &value) { + builder + .emplace( + value->emote, + MessageElementFlags{MessageElementFlag::AlwaysShow, + MessageElementFlag::EmojiAll}) + ->setLink(Link(Link::Type::InsertText, + ":" + value->shortCodes[0] + ":")); + }); + + return builder.release(); + } void addEmoteSets( std::vector> sets, Channel &globalChannel, Channel &subChannel, QString currentChannelName) @@ -126,6 +147,12 @@ namespace { } } } + void addEmotes(Channel &channel, const EmoteMap &map, const QString &title, + const MessageElementFlag &emoteFlag) + { + channel.addMessage(makeTitleMessage(title)); + channel.addMessage(makeEmoteMessage(map, emoteFlag)); + }; } // namespace EmotePopup::EmotePopup(QWidget *parent) @@ -137,40 +164,66 @@ EmotePopup::EmotePopup(QWidget *parent) auto layout = new QVBoxLayout(this); this->getLayoutContainer()->setLayout(layout); - this->notebook_ = new Notebook(this); - layout->addWidget(this->notebook_); - layout->setMargin(0); + QRegularExpression searchRegex("\\S*"); + searchRegex.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + QValidator *searchValidator = new QRegularExpressionValidator(searchRegex); + + this->search_ = new QLineEdit(); + this->search_->setPlaceholderText("Search all emotes..."); + this->search_->setValidator(searchValidator); + this->search_->setClearButtonEnabled(true); + this->search_->findChild()->setIcon( + QPixmap(":/buttons/clearSearch.png")); + layout->addWidget(this->search_); + + QObject::connect(this->search_, &QLineEdit::textChanged, this, + &EmotePopup::filterEmotes); auto clicked = [this](const Link &link) { this->linkClicked.invoke(link); }; - auto makeView = [&](QString tabTitle) { + auto makeView = [&](QString tabTitle, bool addToNotebook = true) { auto view = new ChannelView(); view->setOverrideFlags(MessageElementFlags{ MessageElementFlag::Default, MessageElementFlag::AlwaysShow, MessageElementFlag::EmoteImages}); view->setEnableScrollingToBottom(false); - this->notebook_->addPage(view, tabTitle); view->linkClicked.connect(clicked); + if (addToNotebook) + { + this->notebook_->addPage(view, tabTitle); + } + return view; }; + this->searchView_ = makeView("", false); + this->searchView_->hide(); + layout->addWidget(this->searchView_); + + this->notebook_ = new Notebook(this); + layout->addWidget(this->notebook_); + layout->setMargin(0); + this->subEmotesView_ = makeView("Subs"); this->channelEmotesView_ = makeView("Channel"); this->globalEmotesView_ = makeView("Global"); this->viewEmojis_ = makeView("Emojis"); - this->loadEmojis(); + this->loadEmojis(*this->viewEmojis_, getApp()->emotes->emojis.emojis); this->addShortcuts(); this->signalHolder_.managedConnect(getApp()->hotkeys->onItemsUpdated, [this]() { this->clearShortcuts(); this->addShortcuts(); }); + + this->search_->setFocus(); } + void EmotePopup::addShortcuts() { HotkeyController::HotkeyMap actions{ @@ -252,29 +305,31 @@ void EmotePopup::addShortcuts() {"reject", nullptr}, {"accept", nullptr}, - {"search", nullptr}, + {"search", + [this](std::vector) -> QString { + this->search_->setFocus(); + this->search_->selectAll(); + return ""; + }}, }; this->shortcuts_ = getApp()->hotkeys->shortcutsForCategory( HotkeyCategory::PopupWindow, actions, this); } -void EmotePopup::loadChannel(ChannelPtr _channel) +void EmotePopup::loadChannel(ChannelPtr channel) { BenchmarkGuard guard("loadChannel"); - this->setWindowTitle("Emotes in #" + _channel->getName()); + this->channel_ = channel; + this->twitchChannel_ = dynamic_cast(this->channel_.get()); - auto twitchChannel = dynamic_cast(_channel.get()); - if (twitchChannel == nullptr) - return; + this->setWindowTitle("Emotes in #" + this->channel_->getName()); - auto addEmotes = [&](Channel &channel, const EmoteMap &map, - const QString &title, - const MessageElementFlag &emoteFlag) { - channel.addMessage(makeTitleMessage(title)); - channel.addMessage(makeEmoteMessage(map, emoteFlag)); - }; + if (this->twitchChannel_ == nullptr) + { + return; + } auto subChannel = std::make_shared("", Channel::Type::None); auto globalChannel = std::make_shared("", Channel::Type::None); @@ -283,7 +338,7 @@ void EmotePopup::loadChannel(ChannelPtr _channel) // twitch addEmoteSets( getApp()->accounts->twitch.getCurrent()->accessEmotes()->emoteSets, - *globalChannel, *subChannel, _channel->getName()); + *globalChannel, *subChannel, this->channel_->getName()); // global addEmotes(*globalChannel, *getApp()->twitch2->getBttvEmotes().emotes(), @@ -292,10 +347,10 @@ void EmotePopup::loadChannel(ChannelPtr _channel) "FrankerFaceZ", MessageElementFlag::FfzEmote); // channel - addEmotes(*channelChannel, *twitchChannel->bttvEmotes(), "BetterTTV", + addEmotes(*channelChannel, *this->twitchChannel_->bttvEmotes(), "BetterTTV", MessageElementFlag::BttvEmote); - addEmotes(*channelChannel, *twitchChannel->ffzEmotes(), "FrankerFaceZ", - MessageElementFlag::FfzEmote); + addEmotes(*channelChannel, *this->twitchChannel_->ffzEmotes(), + "FrankerFaceZ", MessageElementFlag::FfzEmote); this->globalEmotesView_->setChannel(globalChannel); this->subEmotesView_->setChannel(subChannel); @@ -313,29 +368,117 @@ void EmotePopup::loadChannel(ChannelPtr _channel) } } -void EmotePopup::loadEmojis() +void EmotePopup::loadEmojis(ChannelView &view, EmojiMap &emojiMap) { - auto &emojis = getApp()->emotes->emojis.emojis; - ChannelPtr emojiChannel(new Channel("", Channel::Type::None)); + emojiChannel->addMessage(makeEmojiMessage(emojiMap)); + + view.setChannel(emojiChannel); +} + +void EmotePopup::loadEmojis(Channel &channel, EmojiMap &emojiMap, + const QString &title) +{ + channel.addMessage(makeTitleMessage(title)); + channel.addMessage(makeEmojiMessage(emojiMap)); +} + +void EmotePopup::filterEmotes(const QString &searchText) +{ + if (searchText.length() == 0) + { + this->notebook_->show(); + this->searchView_->hide(); + + return; + } + + auto searchChannel = std::make_shared("", Channel::Type::None); + + auto twitchEmoteSets = + getApp()->accounts->twitch.getCurrent()->accessEmotes()->emoteSets; + std::vector> twitchGlobalEmotes{}; + + for (const auto &set : twitchEmoteSets) + { + auto setCopy = std::make_shared(*set); + auto setIt = + std::remove_if(setCopy->emotes.begin(), setCopy->emotes.end(), + [searchText](auto &emote) { + return !emote.name.string.contains( + searchText, Qt::CaseInsensitive); + }); + setCopy->emotes.resize(std::distance(setCopy->emotes.begin(), setIt)); + + if (setCopy->emotes.size() > 0) + twitchGlobalEmotes.push_back(setCopy); + } + + auto bttvGlobalEmotes = this->filterEmoteMap( + searchText, getApp()->twitch2->getBttvEmotes().emotes()); + auto ffzGlobalEmotes = this->filterEmoteMap( + searchText, getApp()->twitch2->getFfzEmotes().emotes()); + auto bttvChannelEmotes = + this->filterEmoteMap(searchText, this->twitchChannel_->bttvEmotes()); + auto ffzChannelEmotes = + this->filterEmoteMap(searchText, this->twitchChannel_->ffzEmotes()); + + EmojiMap filteredEmojis{}; + int emojiCount = 0; + + getApp()->emotes->emojis.emojis.each( + [&, searchText](const auto &name, std::shared_ptr &emoji) { + if (emoji->shortCodes[0].contains(searchText, Qt::CaseInsensitive)) + { + filteredEmojis.insert(name, emoji); + emojiCount++; + } + }); + + // twitch + addEmoteSets(twitchGlobalEmotes, *searchChannel, *searchChannel, + this->channel_->getName()); + + // global + if (bttvGlobalEmotes->size() > 0) + addEmotes(*searchChannel, *bttvGlobalEmotes, "BetterTTV (Global)", + MessageElementFlag::BttvEmote); + if (ffzGlobalEmotes->size() > 0) + addEmotes(*searchChannel, *ffzGlobalEmotes, "FrankerFaceZ (Global)", + MessageElementFlag::FfzEmote); + + // channel + if (bttvChannelEmotes->size() > 0) + addEmotes(*searchChannel, *bttvChannelEmotes, "BetterTTV (Channel)", + MessageElementFlag::BttvEmote); + if (ffzChannelEmotes->size() > 0) + addEmotes(*searchChannel, *ffzChannelEmotes, "FrankerFaceZ (Channel)", + MessageElementFlag::FfzEmote); // emojis - MessageBuilder builder; - builder->flags.set(MessageFlag::Centered); - builder->flags.set(MessageFlag::DisableCompactEmotes); - - emojis.each([&builder](const auto &key, const auto &value) { - builder - .emplace( - value->emote, - MessageElementFlags{MessageElementFlag::AlwaysShow, - MessageElementFlag::EmojiAll}) - ->setLink( - Link(Link::Type::InsertText, ":" + value->shortCodes[0] + ":")); - }); - emojiChannel->addMessage(builder.release()); - - this->viewEmojis_->setChannel(emojiChannel); + if (emojiCount > 0) + this->loadEmojis(*searchChannel, filteredEmojis, "Emojis"); + + this->searchView_->setChannel(searchChannel); + + this->notebook_->hide(); + this->searchView_->show(); +} + +EmoteMap *EmotePopup::filterEmoteMap(const QString &text, + std::shared_ptr emotes) +{ + auto filteredMap = new EmoteMap(); + + for (const auto &emote : *emotes) + { + if (emote.first.string.contains(text, Qt::CaseInsensitive)) + { + filteredMap->insert(emote); + } + } + + return filteredMap; } void EmotePopup::closeEvent(QCloseEvent *event) diff --git a/src/widgets/dialogs/EmotePopup.hpp b/src/widgets/dialogs/EmotePopup.hpp index 8ed416bb023..22e18373ca7 100644 --- a/src/widgets/dialogs/EmotePopup.hpp +++ b/src/widgets/dialogs/EmotePopup.hpp @@ -1,10 +1,14 @@ #pragma once +#include "providers/emoji/Emojis.hpp" +#include "providers/twitch/TwitchChannel.hpp" #include "widgets/BasePopup.hpp" #include "widgets/Notebook.hpp" #include +#include + namespace chatterino { struct Link; @@ -18,7 +22,6 @@ class EmotePopup : public BasePopup EmotePopup(QWidget *parent = nullptr); void loadChannel(ChannelPtr channel); - void loadEmojis(); virtual void closeEvent(QCloseEvent *event) override; @@ -29,8 +32,23 @@ class EmotePopup : public BasePopup ChannelView *channelEmotesView_{}; ChannelView *subEmotesView_{}; ChannelView *viewEmojis_{}; + /** + * @brief Visible only when the user has specified a search query into the `search_` input. + * Otherwise the `notebook_` and all other views are visible. + */ + ChannelView *searchView_{}; + + ChannelPtr channel_; + TwitchChannel *twitchChannel_{}; + QLineEdit *search_; Notebook *notebook_; + + void loadEmojis(ChannelView &view, EmojiMap &emojiMap); + void loadEmojis(Channel &channel, EmojiMap &emojiMap, const QString &title); + void filterEmotes(const QString &text); + EmoteMap *filterEmoteMap(const QString &text, + std::shared_ptr emotes); void addShortcuts() override; }; From ac925d28eba40974bebec6243f4b1a30555c50c1 Mon Sep 17 00:00:00 2001 From: Mm2PL Date: Sun, 2 Jan 2022 15:43:51 +0000 Subject: [PATCH 7/7] Add /fakemsg command (#3448) Co-authored-by: pajlada --- CHANGELOG.md | 1 + src/controllers/commands/CommandController.cpp | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52002e0cbdd..9ce31fbc28e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,7 @@ - Dev: Renamed CMake's build option `USE_SYSTEM_QT5KEYCHAIN` to `USE_SYSTEM_QTKEYCHAIN`. (#3103) - Dev: Add benchmarks that can be compiled with the `BUILD_BENCHMARKS` CMake flag. Off by default. (#3038) - Dev: Added CMake build option `BUILD_WITH_QTKEYCHAIN` to build with or without Qt5Keychain support (On by default). (#3318) +- Dev: Added /fakemsg command for debugging (#3448) - Dev: Notebook::select\* functions now take an optional `focusPage` parameter (true by default) which keeps the default behaviour of selecting the page after it has been selected. If set to false, the page is _not_ focused after being selected. (#3446) ## 2.3.4 diff --git a/src/controllers/commands/CommandController.cpp b/src/controllers/commands/CommandController.cpp index 32a15c252a6..98625c3f48d 100644 --- a/src/controllers/commands/CommandController.cpp +++ b/src/controllers/commands/CommandController.cpp @@ -871,6 +871,22 @@ void CommandController::initialize(Settings &, Paths &paths) getApp()->twitch2->sendRawMessage(words.mid(1).join(" ")); return ""; }); +#ifndef NDEBUG + this->registerCommand( + "/fakemsg", + [](const QStringList &words, ChannelPtr channel) -> QString { + if (words.size() < 2) + { + channel->addMessage(makeSystemMessage( + "Usage: /fakemsg (raw irc text) - injects raw irc text as " + "if it was a message received from TMI")); + return ""; + } + auto ircText = words.mid(1).join(" "); + getApp()->twitch2->addFakeMessage(ircText); + return ""; + }); +#endif } void CommandController::save()