mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
feat: add global channel search support (#3694)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
parent
e11677c62b
commit
57783c7478
8 changed files with 135 additions and 36 deletions
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
## Unversioned
|
## Unversioned
|
||||||
|
|
||||||
|
- Major: Added multi-channel searching to search dialog via keyboard shortcut. [Ctrl+Shift+F by default] (#3694)
|
||||||
- Minor: Added `is:first-msg` search option. (#3700)
|
- Minor: Added `is:first-msg` search option. (#3700)
|
||||||
- Minor: Added quotation marks in the permitted/blocked Automod messages for clarity. (#3654)
|
- Minor: Added quotation marks in the permitted/blocked Automod messages for clarity. (#3654)
|
||||||
- Minor: Adjust large stream thumbnail to 16:9 (#3655)
|
- Minor: Adjust large stream thumbnail to 16:9 (#3655)
|
||||||
|
|
|
@ -103,7 +103,8 @@ inline const std::map<HotkeyCategory, ActionDefinitionMap> actionNames{
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
}},
|
}},
|
||||||
{"showSearch", ActionDefinition{"Search"}},
|
{"showSearch", ActionDefinition{"Search current channel"}},
|
||||||
|
{"showGlobalSearch", ActionDefinition{"Search all channels"}},
|
||||||
{"startWatching", ActionDefinition{"Start watching"}},
|
{"startWatching", ActionDefinition{"Start watching"}},
|
||||||
{"debug", ActionDefinition{"Show debug popup"}},
|
{"debug", ActionDefinition{"Show debug popup"}},
|
||||||
}},
|
}},
|
||||||
|
|
|
@ -339,6 +339,9 @@ void HotkeyController::addDefaults(std::set<QString> &addedHotkeys)
|
||||||
this->tryAddDefault(addedHotkeys, HotkeyCategory::Split,
|
this->tryAddDefault(addedHotkeys, HotkeyCategory::Split,
|
||||||
QKeySequence("Ctrl+F"), "showSearch",
|
QKeySequence("Ctrl+F"), "showSearch",
|
||||||
std::vector<QString>(), "show search");
|
std::vector<QString>(), "show search");
|
||||||
|
this->tryAddDefault(addedHotkeys, HotkeyCategory::Split,
|
||||||
|
QKeySequence("Ctrl+Shift+F"), "showGlobalSearch",
|
||||||
|
std::vector<QString>(), "show global search");
|
||||||
this->tryAddDefault(addedHotkeys, HotkeyCategory::Split,
|
this->tryAddDefault(addedHotkeys, HotkeyCategory::Split,
|
||||||
QKeySequence("Ctrl+F5"), "reconnect",
|
QKeySequence("Ctrl+F5"), "reconnect",
|
||||||
std::vector<QString>(), "reconnect");
|
std::vector<QString>(), "reconnect");
|
||||||
|
|
|
@ -81,6 +81,8 @@ public:
|
||||||
void pause(PauseReason reason, boost::optional<uint> msecs = boost::none);
|
void pause(PauseReason reason, boost::optional<uint> msecs = boost::none);
|
||||||
void unpause(PauseReason reason);
|
void unpause(PauseReason reason);
|
||||||
|
|
||||||
|
MessageElementFlags getFlags() const;
|
||||||
|
|
||||||
ChannelPtr channel();
|
ChannelPtr channel();
|
||||||
void setChannel(ChannelPtr channel_);
|
void setChannel(ChannelPtr channel_);
|
||||||
|
|
||||||
|
@ -158,7 +160,6 @@ private:
|
||||||
|
|
||||||
void drawMessages(QPainter &painter);
|
void drawMessages(QPainter &painter);
|
||||||
void setSelection(const SelectionItem &start, const SelectionItem &end);
|
void setSelection(const SelectionItem &start, const SelectionItem &end);
|
||||||
MessageElementFlags getFlags() const;
|
|
||||||
void selectWholeMessage(MessageLayout *layout, int &messageIndex);
|
void selectWholeMessage(MessageLayout *layout, int &messageIndex);
|
||||||
void getWordBounds(MessageLayout *layout,
|
void getWordBounds(MessageLayout *layout,
|
||||||
const MessageLayoutElement *element,
|
const MessageLayoutElement *element,
|
||||||
|
|
|
@ -3,11 +3,9 @@
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QVBoxLayout>
|
|
||||||
|
|
||||||
#include "common/Channel.hpp"
|
#include "common/Channel.hpp"
|
||||||
#include "controllers/hotkeys/HotkeyController.hpp"
|
#include "controllers/hotkeys/HotkeyController.hpp"
|
||||||
#include "messages/Message.hpp"
|
|
||||||
#include "messages/search/AuthorPredicate.hpp"
|
#include "messages/search/AuthorPredicate.hpp"
|
||||||
#include "messages/search/ChannelPredicate.hpp"
|
#include "messages/search/ChannelPredicate.hpp"
|
||||||
#include "messages/search/LinkPredicate.hpp"
|
#include "messages/search/LinkPredicate.hpp"
|
||||||
|
@ -19,8 +17,7 @@
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
ChannelPtr SearchPopup::filter(const QString &text, const QString &channelName,
|
ChannelPtr SearchPopup::filter(const QString &text, const QString &channelName,
|
||||||
const LimitedQueueSnapshot<MessagePtr> &snapshot,
|
const LimitedQueueSnapshot<MessagePtr> &snapshot)
|
||||||
FilterSetPtr filterSet)
|
|
||||||
{
|
{
|
||||||
ChannelPtr channel(new Channel(channelName, Channel::Type::None));
|
ChannelPtr channel(new Channel(channelName, Channel::Type::None));
|
||||||
|
|
||||||
|
@ -44,9 +41,6 @@ ChannelPtr SearchPopup::filter(const QString &text, const QString &channelName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accept && filterSet)
|
|
||||||
accept = filterSet->filter(message, channel);
|
|
||||||
|
|
||||||
// If all predicates match, add the message to the channel
|
// If all predicates match, add the message to the channel
|
||||||
if (accept)
|
if (accept)
|
||||||
channel->addMessage(message);
|
channel->addMessage(message);
|
||||||
|
@ -67,13 +61,13 @@ void SearchPopup::addShortcuts()
|
||||||
{
|
{
|
||||||
HotkeyController::HotkeyMap actions{
|
HotkeyController::HotkeyMap actions{
|
||||||
{"search",
|
{"search",
|
||||||
[this](std::vector<QString>) -> QString {
|
[this](const std::vector<QString> &) -> QString {
|
||||||
this->searchInput_->setFocus();
|
this->searchInput_->setFocus();
|
||||||
this->searchInput_->selectAll();
|
this->searchInput_->selectAll();
|
||||||
return "";
|
return "";
|
||||||
}},
|
}},
|
||||||
{"delete",
|
{"delete",
|
||||||
[this](std::vector<QString>) -> QString {
|
[this](const std::vector<QString> &) -> QString {
|
||||||
this->close();
|
this->close();
|
||||||
return "";
|
return "";
|
||||||
}},
|
}},
|
||||||
|
@ -88,17 +82,25 @@ void SearchPopup::addShortcuts()
|
||||||
HotkeyCategory::PopupWindow, actions, this);
|
HotkeyCategory::PopupWindow, actions, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchPopup::setChannelFilters(FilterSetPtr filters)
|
void SearchPopup::addChannel(ChannelView &channel)
|
||||||
{
|
{
|
||||||
this->channelFilters_ = std::move(filters);
|
if (this->searchChannels_.empty())
|
||||||
}
|
{
|
||||||
|
this->channelView_->setSourceChannel(channel.channel());
|
||||||
|
this->channelName_ = channel.channel()->getName();
|
||||||
|
}
|
||||||
|
else if (this->searchChannels_.size() == 1)
|
||||||
|
{
|
||||||
|
this->channelView_->setSourceChannel(
|
||||||
|
std::make_shared<Channel>("multichannel", Channel::Type::None));
|
||||||
|
|
||||||
void SearchPopup::setChannel(const ChannelPtr &channel)
|
auto flags = this->channelView_->getFlags();
|
||||||
{
|
flags.set(MessageElementFlag::ChannelName);
|
||||||
this->channelView_->setSourceChannel(channel);
|
flags.unset(MessageElementFlag::ModeratorTools);
|
||||||
this->channelName_ = channel->getName();
|
this->channelView_->setOverrideFlags(flags);
|
||||||
this->snapshot_ = channel->getMessageSnapshot();
|
}
|
||||||
this->search();
|
|
||||||
|
this->searchChannels_.append(std::ref(channel));
|
||||||
|
|
||||||
this->updateWindowTitle();
|
this->updateWindowTitle();
|
||||||
}
|
}
|
||||||
|
@ -115,6 +117,10 @@ void SearchPopup::updateWindowTitle()
|
||||||
{
|
{
|
||||||
historyName = "mentions";
|
historyName = "mentions";
|
||||||
}
|
}
|
||||||
|
else if (this->searchChannels_.size() > 1)
|
||||||
|
{
|
||||||
|
historyName = "multiple channels'";
|
||||||
|
}
|
||||||
else if (this->channelName_.isEmpty())
|
else if (this->channelName_.isEmpty())
|
||||||
{
|
{
|
||||||
historyName = "<empty>'s";
|
historyName = "<empty>'s";
|
||||||
|
@ -126,24 +132,90 @@ void SearchPopup::updateWindowTitle()
|
||||||
this->setWindowTitle("Searching in " + historyName + " history");
|
this->setWindowTitle("Searching in " + historyName + " history");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SearchPopup::showEvent(QShowEvent *)
|
||||||
|
{
|
||||||
|
this->search();
|
||||||
|
}
|
||||||
|
|
||||||
void SearchPopup::search()
|
void SearchPopup::search()
|
||||||
{
|
{
|
||||||
|
if (this->snapshot_.size() == 0)
|
||||||
|
{
|
||||||
|
this->snapshot_ = this->buildSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
this->channelView_->setChannel(filter(this->searchInput_->text(),
|
this->channelView_->setChannel(filter(this->searchInput_->text(),
|
||||||
this->channelName_, this->snapshot_,
|
this->channelName_, this->snapshot_));
|
||||||
this->channelFilters_));
|
}
|
||||||
|
|
||||||
|
LimitedQueueSnapshot<MessagePtr> SearchPopup::buildSnapshot()
|
||||||
|
{
|
||||||
|
// no point in filtering/sorting if it's a single channel search
|
||||||
|
if (this->searchChannels_.length() == 1)
|
||||||
|
{
|
||||||
|
const auto channelPtr = this->searchChannels_.at(0);
|
||||||
|
return channelPtr.get().channel()->getMessageSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto combinedSnapshot = std::vector<std::shared_ptr<const Message>>{};
|
||||||
|
for (auto &channel : this->searchChannels_)
|
||||||
|
{
|
||||||
|
ChannelView &sharedView = channel.get();
|
||||||
|
|
||||||
|
const FilterSetPtr filterSet = sharedView.getFilterSet();
|
||||||
|
const LimitedQueueSnapshot<MessagePtr> &snapshot =
|
||||||
|
sharedView.channel()->getMessageSnapshot();
|
||||||
|
|
||||||
|
// TODO: implement iterator on LimitedQueueSnapshot?
|
||||||
|
for (auto i = 0; i < snapshot.size(); ++i)
|
||||||
|
{
|
||||||
|
const MessagePtr &message = snapshot[i];
|
||||||
|
if (filterSet && !filterSet->filter(message, sharedView.channel()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
combinedSnapshot.push_back(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove any duplicate messages from splits containing the same channel
|
||||||
|
std::sort(combinedSnapshot.begin(), combinedSnapshot.end(),
|
||||||
|
[](MessagePtr &a, MessagePtr &b) {
|
||||||
|
return a->id > b->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto uniqueIterator =
|
||||||
|
std::unique(combinedSnapshot.begin(), combinedSnapshot.end(),
|
||||||
|
[](MessagePtr &a, MessagePtr &b) {
|
||||||
|
return a->id == b->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
combinedSnapshot.erase(uniqueIterator, combinedSnapshot.end());
|
||||||
|
|
||||||
|
// resort by time for presentation
|
||||||
|
std::sort(combinedSnapshot.begin(), combinedSnapshot.end(),
|
||||||
|
[](MessagePtr &a, MessagePtr &b) {
|
||||||
|
return a->serverReceivedTime < b->serverReceivedTime;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto queue = LimitedQueue<MessagePtr>(combinedSnapshot.size());
|
||||||
|
queue.pushFront(combinedSnapshot);
|
||||||
|
|
||||||
|
return queue.getSnapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchPopup::initLayout()
|
void SearchPopup::initLayout()
|
||||||
{
|
{
|
||||||
// VBOX
|
// VBOX
|
||||||
{
|
{
|
||||||
QVBoxLayout *layout1 = new QVBoxLayout(this);
|
auto *layout1 = new QVBoxLayout(this);
|
||||||
layout1->setMargin(0);
|
layout1->setMargin(0);
|
||||||
layout1->setSpacing(0);
|
layout1->setSpacing(0);
|
||||||
|
|
||||||
// HBOX
|
// HBOX
|
||||||
{
|
{
|
||||||
QHBoxLayout *layout2 = new QHBoxLayout(this);
|
auto *layout2 = new QHBoxLayout(this);
|
||||||
layout2->setMargin(8);
|
layout2->setMargin(8);
|
||||||
layout2->setSpacing(8);
|
layout2->setSpacing(8);
|
||||||
|
|
||||||
|
|
|
@ -17,16 +17,17 @@ class SearchPopup : public BasePopup
|
||||||
public:
|
public:
|
||||||
SearchPopup(QWidget *parent);
|
SearchPopup(QWidget *parent);
|
||||||
|
|
||||||
virtual void setChannel(const ChannelPtr &channel);
|
virtual void addChannel(ChannelView &channel);
|
||||||
virtual void setChannelFilters(FilterSetPtr filters);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void updateWindowTitle();
|
virtual void updateWindowTitle();
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initLayout();
|
void initLayout();
|
||||||
void search();
|
void search();
|
||||||
void addShortcuts() override;
|
void addShortcuts() override;
|
||||||
|
LimitedQueueSnapshot<MessagePtr> buildSnapshot();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Only retains those message from a list of messages that satisfy a
|
* @brief Only retains those message from a list of messages that satisfy a
|
||||||
|
@ -41,8 +42,7 @@ private:
|
||||||
* "snapshot"
|
* "snapshot"
|
||||||
*/
|
*/
|
||||||
static ChannelPtr filter(const QString &text, const QString &channelName,
|
static ChannelPtr filter(const QString &text, const QString &channelName,
|
||||||
const LimitedQueueSnapshot<MessagePtr> &snapshot,
|
const LimitedQueueSnapshot<MessagePtr> &snapshot);
|
||||||
FilterSetPtr filterSet);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Checks the input for tags and registers their corresponding
|
* @brief Checks the input for tags and registers their corresponding
|
||||||
|
@ -58,7 +58,7 @@ private:
|
||||||
QLineEdit *searchInput_{};
|
QLineEdit *searchInput_{};
|
||||||
ChannelView *channelView_{};
|
ChannelView *channelView_{};
|
||||||
QString channelName_{};
|
QString channelName_{};
|
||||||
FilterSetPtr channelFilters_;
|
QList<std::reference_wrapper<ChannelView>> searchChannels_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -273,7 +273,12 @@ void Split::addShortcuts()
|
||||||
}},
|
}},
|
||||||
{"showSearch",
|
{"showSearch",
|
||||||
[this](std::vector<QString>) -> QString {
|
[this](std::vector<QString>) -> QString {
|
||||||
this->showSearch();
|
this->showSearch(true);
|
||||||
|
return "";
|
||||||
|
}},
|
||||||
|
{"showGlobalSearch",
|
||||||
|
[this](std::vector<QString>) -> QString {
|
||||||
|
this->showSearch(false);
|
||||||
return "";
|
return "";
|
||||||
}},
|
}},
|
||||||
{"reconnect",
|
{"reconnect",
|
||||||
|
@ -1155,13 +1160,29 @@ const QList<QUuid> Split::getFilters() const
|
||||||
return this->view_->getFilterIds();
|
return this->view_->getFilterIds();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Split::showSearch()
|
void Split::showSearch(bool singleChannel)
|
||||||
{
|
{
|
||||||
SearchPopup *popup = new SearchPopup(this);
|
auto *popup = new SearchPopup(this);
|
||||||
|
|
||||||
popup->setChannelFilters(this->view_->getFilterSet());
|
|
||||||
popup->setAttribute(Qt::WA_DeleteOnClose);
|
popup->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
popup->setChannel(this->getChannel());
|
|
||||||
|
if (singleChannel)
|
||||||
|
{
|
||||||
|
popup->addChannel(this->getChannelView());
|
||||||
|
popup->show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass every ChannelView for every Split across the app to the search popup
|
||||||
|
auto ¬ebook = getApp()->windows->getMainWindow().getNotebook();
|
||||||
|
for (int i = 0; i < notebook.getPageCount(); ++i)
|
||||||
|
{
|
||||||
|
auto container = dynamic_cast<SplitContainer *>(notebook.getPageAt(i));
|
||||||
|
for (auto split : container->getSplits())
|
||||||
|
{
|
||||||
|
popup->addChannel(split->getChannelView());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
popup->show();
|
popup->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -171,7 +171,7 @@ public slots:
|
||||||
void copyToClipboard();
|
void copyToClipboard();
|
||||||
void startWatching();
|
void startWatching();
|
||||||
void setFiltersDialog();
|
void setFiltersDialog();
|
||||||
void showSearch();
|
void showSearch(bool singleChannel);
|
||||||
void showViewerList();
|
void showViewerList();
|
||||||
void openSubPage();
|
void openSubPage();
|
||||||
void reloadChannelAndSubscriberEmotes();
|
void reloadChannelAndSubscriberEmotes();
|
||||||
|
|
Loading…
Reference in a new issue