2019-09-08 22:27:57 +02:00
|
|
|
#include "SearchPopup.hpp"
|
|
|
|
|
|
|
|
#include <QHBoxLayout>
|
|
|
|
#include <QLineEdit>
|
|
|
|
#include <QPushButton>
|
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
|
|
|
#include "common/Channel.hpp"
|
|
|
|
#include "messages/Message.hpp"
|
2019-09-09 15:21:49 +02:00
|
|
|
#include "messages/search/AuthorPredicate.hpp"
|
|
|
|
#include "messages/search/LinkPredicate.hpp"
|
|
|
|
#include "messages/search/SubstringPredicate.hpp"
|
2020-07-18 17:52:12 +02:00
|
|
|
#include "util/Shortcut.hpp"
|
2019-09-08 22:27:57 +02:00
|
|
|
#include "widgets/helper/ChannelView.hpp"
|
|
|
|
|
|
|
|
namespace chatterino {
|
2019-09-09 15:21:49 +02:00
|
|
|
|
|
|
|
ChannelPtr SearchPopup::filter(const QString &text, const QString &channelName,
|
2020-10-18 15:16:56 +02:00
|
|
|
const LimitedQueueSnapshot<MessagePtr> &snapshot,
|
|
|
|
FilterSetPtr filterSet)
|
2019-09-09 15:21:49 +02:00
|
|
|
{
|
|
|
|
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)
|
2019-09-08 22:27:57 +02:00
|
|
|
{
|
2019-09-09 15:21:49 +02:00
|
|
|
MessagePtr message = snapshot[i];
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2019-09-09 15:21:49 +02:00
|
|
|
bool accept = true;
|
|
|
|
for (const auto &pred : predicates)
|
2019-09-08 22:27:57 +02:00
|
|
|
{
|
2019-09-09 15:21:49 +02:00
|
|
|
// Discard the message as soon as one predicate fails
|
|
|
|
if (!pred->appliesTo(*message))
|
2019-09-08 22:27:57 +02:00
|
|
|
{
|
2019-09-09 15:21:49 +02:00
|
|
|
accept = false;
|
|
|
|
break;
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-18 15:16:56 +02:00
|
|
|
if (accept && filterSet)
|
|
|
|
accept = filterSet->filter(message);
|
|
|
|
|
2019-09-09 15:21:49 +02:00
|
|
|
// If all predicates match, add the message to the channel
|
|
|
|
if (accept)
|
|
|
|
channel->addMessage(message);
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
2019-09-09 15:21:49 +02:00
|
|
|
|
|
|
|
return channel;
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2020-11-15 11:07:20 +01:00
|
|
|
SearchPopup::SearchPopup(QWidget *parent)
|
|
|
|
: BasePopup({}, parent)
|
2019-09-08 22:27:57 +02:00
|
|
|
{
|
|
|
|
this->initLayout();
|
|
|
|
this->resize(400, 600);
|
2020-07-18 17:52:12 +02:00
|
|
|
|
|
|
|
createShortcut(this, "CTRL+F", [this] {
|
|
|
|
this->searchInput_->setFocus();
|
|
|
|
this->searchInput_->selectAll();
|
|
|
|
});
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
|
2020-10-18 15:16:56 +02:00
|
|
|
void SearchPopup::setChannelFilters(FilterSetPtr filters)
|
|
|
|
{
|
2021-04-10 14:34:40 +02:00
|
|
|
this->channelFilters_ = std::move(filters);
|
2020-10-18 15:16:56 +02:00
|
|
|
}
|
|
|
|
|
2019-09-08 22:27:57 +02:00
|
|
|
void SearchPopup::setChannel(const ChannelPtr &channel)
|
|
|
|
{
|
2020-10-25 11:10:28 +01:00
|
|
|
this->channelView_->setSourceChannel(channel);
|
2019-09-08 22:27:57 +02:00
|
|
|
this->channelName_ = channel->getName();
|
|
|
|
this->snapshot_ = channel->getMessageSnapshot();
|
|
|
|
this->search();
|
|
|
|
|
|
|
|
this->updateWindowTitle();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SearchPopup::updateWindowTitle()
|
|
|
|
{
|
2020-12-12 13:06:40 +01:00
|
|
|
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");
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void SearchPopup::search()
|
|
|
|
{
|
|
|
|
this->channelView_->setChannel(filter(this->searchInput_->text(),
|
2020-10-18 15:16:56 +02:00
|
|
|
this->channelName_, this->snapshot_,
|
|
|
|
this->channelFilters_));
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2020-11-08 12:02:19 +01:00
|
|
|
[this] {
|
|
|
|
this->search();
|
|
|
|
});
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SEARCH BUTTON
|
|
|
|
{
|
|
|
|
QPushButton *searchButton = new QPushButton(this);
|
|
|
|
searchButton->setText("Search");
|
|
|
|
layout2->addWidget(searchButton);
|
2020-11-08 12:02:19 +01:00
|
|
|
QObject::connect(searchButton, &QPushButton::clicked, [this] {
|
|
|
|
this->search();
|
|
|
|
});
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
layout1->addLayout(layout2);
|
|
|
|
}
|
|
|
|
|
|
|
|
// CHANNELVIEW
|
|
|
|
{
|
|
|
|
this->channelView_ = new ChannelView(this);
|
|
|
|
|
|
|
|
layout1->addWidget(this->channelView_);
|
|
|
|
}
|
|
|
|
|
|
|
|
this->setLayout(layout1);
|
|
|
|
}
|
2021-04-11 15:37:53 +02:00
|
|
|
|
|
|
|
this->searchInput_->setFocus();
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
|
2019-09-09 15:21:49 +02:00
|
|
|
std::vector<std::unique_ptr<MessagePredicate>> SearchPopup::parsePredicates(
|
|
|
|
const QString &input)
|
|
|
|
{
|
|
|
|
static QRegularExpression predicateRegex(R"(^(\w+):([\w,]+)$)");
|
|
|
|
|
|
|
|
auto predicates = std::vector<std::unique_ptr<MessagePredicate>>();
|
|
|
|
auto words = input.split(' ', QString::SkipEmptyParts);
|
|
|
|
auto authors = QStringList();
|
|
|
|
|
|
|
|
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
|
|
|
|
{
|
|
|
|
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 (!words.empty())
|
|
|
|
predicates.push_back(
|
|
|
|
std::make_unique<SubstringPredicate>(words.join(" ")));
|
|
|
|
|
|
|
|
return predicates;
|
|
|
|
}
|
|
|
|
|
2019-09-08 22:27:57 +02:00
|
|
|
} // namespace chatterino
|