mirror-chatterino2/src/widgets/helper/SearchPopup.cpp

229 lines
6 KiB
C++

#include "SearchPopup.hpp"
#include <QHBoxLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include "common/Channel.hpp"
#include "messages/Message.hpp"
#include "messages/search/AuthorPredicate.hpp"
#include "messages/search/ChannelPredicate.hpp"
#include "messages/search/LinkPredicate.hpp"
#include "messages/search/MessageFlagsPredicate.hpp"
#include "messages/search/SubstringPredicate.hpp"
#include "util/Shortcut.hpp"
#include "widgets/helper/ChannelView.hpp"
namespace chatterino {
ChannelPtr SearchPopup::filter(const QString &text, const QString &channelName,
const LimitedQueueSnapshot<MessagePtr> &snapshot,
FilterSetPtr filterSet)
{
ChannelPtr channel(new Channel(channelName, Channel::Type::None));
// Parse predicates from tags in "text"
auto predicates = parsePredicates(text);
// Check for every message whether it fulfills all predicates that have
// been registered
for (size_t i = 0; i < snapshot.size(); ++i)
{
MessagePtr message = snapshot[i];
bool accept = true;
for (const auto &pred : predicates)
{
// Discard the message as soon as one predicate fails
if (!pred->appliesTo(*message))
{
accept = false;
break;
}
}
if (accept && filterSet)
accept = filterSet->filter(message, channel);
// If all predicates match, add the message to the channel
if (accept)
channel->addMessage(message);
}
return channel;
}
SearchPopup::SearchPopup(QWidget *parent)
: BasePopup({}, parent)
{
this->initLayout();
this->resize(400, 600);
createShortcut(this, "CTRL+F", [this] {
this->searchInput_->setFocus();
this->searchInput_->selectAll();
});
}
void SearchPopup::setChannelFilters(FilterSetPtr filters)
{
this->channelFilters_ = std::move(filters);
}
void SearchPopup::setChannel(const ChannelPtr &channel)
{
this->channelView_->setSourceChannel(channel);
this->channelName_ = channel->getName();
this->snapshot_ = channel->getMessageSnapshot();
this->search();
this->updateWindowTitle();
}
void SearchPopup::updateWindowTitle()
{
QString historyName;
if (this->channelName_ == "/whispers")
{
historyName = "whispers";
}
else if (this->channelName_ == "/mentions")
{
historyName = "mentions";
}
else if (this->channelName_.isEmpty())
{
historyName = "<empty>'s";
}
else
{
historyName = QString("%1's").arg(this->channelName_);
}
this->setWindowTitle("Searching in " + historyName + " history");
}
void SearchPopup::search()
{
this->channelView_->setChannel(filter(this->searchInput_->text(),
this->channelName_, this->snapshot_,
this->channelFilters_));
}
void SearchPopup::initLayout()
{
// VBOX
{
QVBoxLayout *layout1 = new QVBoxLayout(this);
layout1->setMargin(0);
layout1->setSpacing(0);
// HBOX
{
QHBoxLayout *layout2 = new QHBoxLayout(this);
layout2->setMargin(8);
layout2->setSpacing(8);
// SEARCH INPUT
{
this->searchInput_ = new QLineEdit(this);
layout2->addWidget(this->searchInput_);
QObject::connect(this->searchInput_, &QLineEdit::returnPressed,
[this] {
this->search();
});
}
// SEARCH BUTTON
{
QPushButton *searchButton = new QPushButton(this);
searchButton->setText("Search");
layout2->addWidget(searchButton);
QObject::connect(searchButton, &QPushButton::clicked, [this] {
this->search();
});
}
layout1->addLayout(layout2);
}
// CHANNELVIEW
{
this->channelView_ = new ChannelView(this);
layout1->addWidget(this->channelView_);
}
this->setLayout(layout1);
}
this->searchInput_->setFocus();
}
std::vector<std::unique_ptr<MessagePredicate>> SearchPopup::parsePredicates(
const QString &input)
{
static QRegularExpression predicateRegex(R"(^(\w+):([\w,]+)$)");
std::vector<std::unique_ptr<MessagePredicate>> predicates;
auto words = input.split(' ', QString::SkipEmptyParts);
QStringList authors;
QStringList channels;
for (auto it = words.begin(); it != words.end();)
{
if (auto match = predicateRegex.match(*it); match.hasMatch())
{
QString name = match.captured(1);
QString value = match.captured(2);
bool remove = true;
// match predicates
if (name == "from")
{
authors.append(value);
}
else if (name == "has" && value == "link")
{
predicates.push_back(std::make_unique<LinkPredicate>());
}
else if (name == "in")
{
channels.append(value);
}
else if (name == "is")
{
predicates.push_back(
std::make_unique<MessageFlagsPredicate>(value));
}
else
{
remove = false;
}
// remove or advance
it = remove ? words.erase(it) : ++it;
}
else
{
++it;
}
}
if (!authors.empty())
predicates.push_back(std::make_unique<AuthorPredicate>(authors));
if (!channels.empty())
predicates.push_back(std::make_unique<ChannelPredicate>(channels));
if (!words.empty())
predicates.push_back(
std::make_unique<SubstringPredicate>(words.join(" ")));
return predicates;
}
} // namespace chatterino