mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
Add search to emote popup (#3404)
Co-authored-by: Paweł <zneix@zneix.eu> Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
parent
2b9e2bd1b0
commit
8e5468c316
|
@ -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: 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: Sorted usernames in `Users joined/parted` messages alphabetically. (#3421)
|
||||||
- Minor: Mod list, VIP list, and Users joined/parted messages are now searchable. (#3426)
|
- 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)
|
- 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: Fix Split Input hotkeys not being available when input is hidden (#3362)
|
||||||
- Bugfix: Fixed colored usernames sometimes not working. (#3170)
|
- Bugfix: Fixed colored usernames sometimes not working. (#3170)
|
||||||
|
|
|
@ -16,7 +16,10 @@
|
||||||
#include "widgets/Scrollbar.hpp"
|
#include "widgets/Scrollbar.hpp"
|
||||||
#include "widgets/helper/ChannelView.hpp"
|
#include "widgets/helper/ChannelView.hpp"
|
||||||
|
|
||||||
|
#include <QAbstractButton>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QRegularExpressionValidator>
|
||||||
#include <QTabWidget>
|
#include <QTabWidget>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
@ -62,6 +65,24 @@ namespace {
|
||||||
|
|
||||||
return builder.release();
|
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<EmoteElement>(
|
||||||
|
value->emote,
|
||||||
|
MessageElementFlags{MessageElementFlag::AlwaysShow,
|
||||||
|
MessageElementFlag::EmojiAll})
|
||||||
|
->setLink(Link(Link::Type::InsertText,
|
||||||
|
":" + value->shortCodes[0] + ":"));
|
||||||
|
});
|
||||||
|
|
||||||
|
return builder.release();
|
||||||
|
}
|
||||||
void addEmoteSets(
|
void addEmoteSets(
|
||||||
std::vector<std::shared_ptr<TwitchAccount::EmoteSet>> sets,
|
std::vector<std::shared_ptr<TwitchAccount::EmoteSet>> sets,
|
||||||
Channel &globalChannel, Channel &subChannel, QString currentChannelName)
|
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
|
} // namespace
|
||||||
|
|
||||||
EmotePopup::EmotePopup(QWidget *parent)
|
EmotePopup::EmotePopup(QWidget *parent)
|
||||||
|
@ -137,40 +164,66 @@ EmotePopup::EmotePopup(QWidget *parent)
|
||||||
auto layout = new QVBoxLayout(this);
|
auto layout = new QVBoxLayout(this);
|
||||||
this->getLayoutContainer()->setLayout(layout);
|
this->getLayoutContainer()->setLayout(layout);
|
||||||
|
|
||||||
this->notebook_ = new Notebook(this);
|
QRegularExpression searchRegex("\\S*");
|
||||||
layout->addWidget(this->notebook_);
|
searchRegex.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
|
||||||
layout->setMargin(0);
|
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<QAbstractButton *>()->setIcon(
|
||||||
|
QPixmap(":/buttons/clearSearch.png"));
|
||||||
|
layout->addWidget(this->search_);
|
||||||
|
|
||||||
|
QObject::connect(this->search_, &QLineEdit::textChanged, this,
|
||||||
|
&EmotePopup::filterEmotes);
|
||||||
|
|
||||||
auto clicked = [this](const Link &link) {
|
auto clicked = [this](const Link &link) {
|
||||||
this->linkClicked.invoke(link);
|
this->linkClicked.invoke(link);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto makeView = [&](QString tabTitle) {
|
auto makeView = [&](QString tabTitle, bool addToNotebook = true) {
|
||||||
auto view = new ChannelView();
|
auto view = new ChannelView();
|
||||||
|
|
||||||
view->setOverrideFlags(MessageElementFlags{
|
view->setOverrideFlags(MessageElementFlags{
|
||||||
MessageElementFlag::Default, MessageElementFlag::AlwaysShow,
|
MessageElementFlag::Default, MessageElementFlag::AlwaysShow,
|
||||||
MessageElementFlag::EmoteImages});
|
MessageElementFlag::EmoteImages});
|
||||||
view->setEnableScrollingToBottom(false);
|
view->setEnableScrollingToBottom(false);
|
||||||
this->notebook_->addPage(view, tabTitle);
|
|
||||||
view->linkClicked.connect(clicked);
|
view->linkClicked.connect(clicked);
|
||||||
|
|
||||||
|
if (addToNotebook)
|
||||||
|
{
|
||||||
|
this->notebook_->addPage(view, tabTitle);
|
||||||
|
}
|
||||||
|
|
||||||
return view;
|
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->subEmotesView_ = makeView("Subs");
|
||||||
this->channelEmotesView_ = makeView("Channel");
|
this->channelEmotesView_ = makeView("Channel");
|
||||||
this->globalEmotesView_ = makeView("Global");
|
this->globalEmotesView_ = makeView("Global");
|
||||||
this->viewEmojis_ = makeView("Emojis");
|
this->viewEmojis_ = makeView("Emojis");
|
||||||
|
|
||||||
this->loadEmojis();
|
this->loadEmojis(*this->viewEmojis_, getApp()->emotes->emojis.emojis);
|
||||||
this->addShortcuts();
|
this->addShortcuts();
|
||||||
this->signalHolder_.managedConnect(getApp()->hotkeys->onItemsUpdated,
|
this->signalHolder_.managedConnect(getApp()->hotkeys->onItemsUpdated,
|
||||||
[this]() {
|
[this]() {
|
||||||
this->clearShortcuts();
|
this->clearShortcuts();
|
||||||
this->addShortcuts();
|
this->addShortcuts();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this->search_->setFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmotePopup::addShortcuts()
|
void EmotePopup::addShortcuts()
|
||||||
{
|
{
|
||||||
HotkeyController::HotkeyMap actions{
|
HotkeyController::HotkeyMap actions{
|
||||||
|
@ -252,29 +305,31 @@ void EmotePopup::addShortcuts()
|
||||||
|
|
||||||
{"reject", nullptr},
|
{"reject", nullptr},
|
||||||
{"accept", nullptr},
|
{"accept", nullptr},
|
||||||
{"search", nullptr},
|
{"search",
|
||||||
|
[this](std::vector<QString>) -> QString {
|
||||||
|
this->search_->setFocus();
|
||||||
|
this->search_->selectAll();
|
||||||
|
return "";
|
||||||
|
}},
|
||||||
};
|
};
|
||||||
|
|
||||||
this->shortcuts_ = getApp()->hotkeys->shortcutsForCategory(
|
this->shortcuts_ = getApp()->hotkeys->shortcutsForCategory(
|
||||||
HotkeyCategory::PopupWindow, actions, this);
|
HotkeyCategory::PopupWindow, actions, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmotePopup::loadChannel(ChannelPtr _channel)
|
void EmotePopup::loadChannel(ChannelPtr channel)
|
||||||
{
|
{
|
||||||
BenchmarkGuard guard("loadChannel");
|
BenchmarkGuard guard("loadChannel");
|
||||||
|
|
||||||
this->setWindowTitle("Emotes in #" + _channel->getName());
|
this->channel_ = channel;
|
||||||
|
this->twitchChannel_ = dynamic_cast<TwitchChannel *>(this->channel_.get());
|
||||||
|
|
||||||
auto twitchChannel = dynamic_cast<TwitchChannel *>(_channel.get());
|
this->setWindowTitle("Emotes in #" + this->channel_->getName());
|
||||||
if (twitchChannel == nullptr)
|
|
||||||
|
if (this->twitchChannel_ == nullptr)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
auto addEmotes = [&](Channel &channel, const EmoteMap &map,
|
|
||||||
const QString &title,
|
|
||||||
const MessageElementFlag &emoteFlag) {
|
|
||||||
channel.addMessage(makeTitleMessage(title));
|
|
||||||
channel.addMessage(makeEmoteMessage(map, emoteFlag));
|
|
||||||
};
|
|
||||||
|
|
||||||
auto subChannel = std::make_shared<Channel>("", Channel::Type::None);
|
auto subChannel = std::make_shared<Channel>("", Channel::Type::None);
|
||||||
auto globalChannel = std::make_shared<Channel>("", Channel::Type::None);
|
auto globalChannel = std::make_shared<Channel>("", Channel::Type::None);
|
||||||
|
@ -283,7 +338,7 @@ void EmotePopup::loadChannel(ChannelPtr _channel)
|
||||||
// twitch
|
// twitch
|
||||||
addEmoteSets(
|
addEmoteSets(
|
||||||
getApp()->accounts->twitch.getCurrent()->accessEmotes()->emoteSets,
|
getApp()->accounts->twitch.getCurrent()->accessEmotes()->emoteSets,
|
||||||
*globalChannel, *subChannel, _channel->getName());
|
*globalChannel, *subChannel, this->channel_->getName());
|
||||||
|
|
||||||
// global
|
// global
|
||||||
addEmotes(*globalChannel, *getApp()->twitch2->getBttvEmotes().emotes(),
|
addEmotes(*globalChannel, *getApp()->twitch2->getBttvEmotes().emotes(),
|
||||||
|
@ -292,10 +347,10 @@ void EmotePopup::loadChannel(ChannelPtr _channel)
|
||||||
"FrankerFaceZ", MessageElementFlag::FfzEmote);
|
"FrankerFaceZ", MessageElementFlag::FfzEmote);
|
||||||
|
|
||||||
// channel
|
// channel
|
||||||
addEmotes(*channelChannel, *twitchChannel->bttvEmotes(), "BetterTTV",
|
addEmotes(*channelChannel, *this->twitchChannel_->bttvEmotes(), "BetterTTV",
|
||||||
MessageElementFlag::BttvEmote);
|
MessageElementFlag::BttvEmote);
|
||||||
addEmotes(*channelChannel, *twitchChannel->ffzEmotes(), "FrankerFaceZ",
|
addEmotes(*channelChannel, *this->twitchChannel_->ffzEmotes(),
|
||||||
MessageElementFlag::FfzEmote);
|
"FrankerFaceZ", MessageElementFlag::FfzEmote);
|
||||||
|
|
||||||
this->globalEmotesView_->setChannel(globalChannel);
|
this->globalEmotesView_->setChannel(globalChannel);
|
||||||
this->subEmotesView_->setChannel(subChannel);
|
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));
|
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>("", Channel::Type::None);
|
||||||
|
|
||||||
|
auto twitchEmoteSets =
|
||||||
|
getApp()->accounts->twitch.getCurrent()->accessEmotes()->emoteSets;
|
||||||
|
std::vector<std::shared_ptr<TwitchAccount::EmoteSet>> twitchGlobalEmotes{};
|
||||||
|
|
||||||
|
for (const auto &set : twitchEmoteSets)
|
||||||
|
{
|
||||||
|
auto setCopy = std::make_shared<TwitchAccount::EmoteSet>(*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<EmojiData> &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
|
// emojis
|
||||||
MessageBuilder builder;
|
if (emojiCount > 0)
|
||||||
builder->flags.set(MessageFlag::Centered);
|
this->loadEmojis(*searchChannel, filteredEmojis, "Emojis");
|
||||||
builder->flags.set(MessageFlag::DisableCompactEmotes);
|
|
||||||
|
|
||||||
emojis.each([&builder](const auto &key, const auto &value) {
|
this->searchView_->setChannel(searchChannel);
|
||||||
builder
|
|
||||||
.emplace<EmoteElement>(
|
|
||||||
value->emote,
|
|
||||||
MessageElementFlags{MessageElementFlag::AlwaysShow,
|
|
||||||
MessageElementFlag::EmojiAll})
|
|
||||||
->setLink(
|
|
||||||
Link(Link::Type::InsertText, ":" + value->shortCodes[0] + ":"));
|
|
||||||
});
|
|
||||||
emojiChannel->addMessage(builder.release());
|
|
||||||
|
|
||||||
this->viewEmojis_->setChannel(emojiChannel);
|
this->notebook_->hide();
|
||||||
|
this->searchView_->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
EmoteMap *EmotePopup::filterEmoteMap(const QString &text,
|
||||||
|
std::shared_ptr<const EmoteMap> 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)
|
void EmotePopup::closeEvent(QCloseEvent *event)
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "providers/emoji/Emojis.hpp"
|
||||||
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "widgets/BasePopup.hpp"
|
#include "widgets/BasePopup.hpp"
|
||||||
#include "widgets/Notebook.hpp"
|
#include "widgets/Notebook.hpp"
|
||||||
|
|
||||||
#include <pajlada/signals/signal.hpp>
|
#include <pajlada/signals/signal.hpp>
|
||||||
|
|
||||||
|
#include <QLineEdit>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
struct Link;
|
struct Link;
|
||||||
|
@ -18,7 +22,6 @@ public:
|
||||||
EmotePopup(QWidget *parent = nullptr);
|
EmotePopup(QWidget *parent = nullptr);
|
||||||
|
|
||||||
void loadChannel(ChannelPtr channel);
|
void loadChannel(ChannelPtr channel);
|
||||||
void loadEmojis();
|
|
||||||
|
|
||||||
virtual void closeEvent(QCloseEvent *event) override;
|
virtual void closeEvent(QCloseEvent *event) override;
|
||||||
|
|
||||||
|
@ -29,8 +32,23 @@ private:
|
||||||
ChannelView *channelEmotesView_{};
|
ChannelView *channelEmotesView_{};
|
||||||
ChannelView *subEmotesView_{};
|
ChannelView *subEmotesView_{};
|
||||||
ChannelView *viewEmojis_{};
|
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_;
|
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<const EmoteMap> emotes);
|
||||||
void addShortcuts() override;
|
void addShortcuts() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue