diff --git a/src/Cache.cc b/src/Cache.cc
index ea31e749..416d95a6 100644
--- a/src/Cache.cc
+++ b/src/Cache.cc
@@ -142,6 +142,27 @@ Cache::saveImage(const QString &url, const QByteArray &image)
}
QByteArray
+Cache::image(lmdb::txn &txn, const std::string &url) const
+{
+ if (url.empty())
+ return QByteArray();
+
+ try {
+ lmdb::val image;
+ bool res = lmdb::dbi_get(txn, mediaDb_, lmdb::val(url), image);
+
+ if (!res)
+ return QByteArray();
+
+ return QByteArray(image.data(), image.size());
+ } catch (const lmdb::error &e) {
+ qCritical() << "image:" << e.what() << QString::fromStdString(url);
+ }
+
+ return QByteArray();
+}
+
+QByteArray
Cache::image(const QString &url) const
{
if (url.isEmpty())
@@ -945,10 +966,47 @@ Cache::populateMembers()
txn.commit();
}
+std::vector<RoomSearchResult>
+Cache::searchRooms(const std::string &query, std::uint8_t max_items)
+{
+ std::multimap<int, std::pair<std::string, RoomInfo>> items;
+
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ auto cursor = lmdb::cursor::open(txn, roomsDb_);
+
+ std::string room_id, room_data;
+ while (cursor.get(room_id, room_data, MDB_NEXT)) {
+ RoomInfo tmp = json::parse(std::move(room_data));
+
+ const int score = utils::levenshtein_distance(
+ query, QString::fromStdString(tmp.name).toLower().toStdString());
+ items.emplace(score, std::make_pair(room_id, tmp));
+ }
+
+ cursor.close();
+
+ auto end = items.begin();
+
+ if (items.size() >= max_items)
+ std::advance(end, max_items);
+ else if (items.size() > 0)
+ std::advance(end, items.size());
+
+ std::vector<RoomSearchResult> results;
+ for (auto it = items.begin(); it != end; it++) {
+ results.push_back(
+ RoomSearchResult{it->second.first,
+ it->second.second,
+ QImage::fromData(image(txn, it->second.second.avatar_url))});
+ }
+
+ txn.commit();
+
+ return results;
+}
+
QVector<SearchResult>
-Cache::getAutocompleteMatches(const std::string &room_id,
- const std::string &query,
- std::uint8_t max_items)
+Cache::searchUsers(const std::string &room_id, const std::string &query, std::uint8_t max_items)
{
std::multimap<int, std::pair<std::string, std::string>> items;
diff --git a/src/ChatPage.cc b/src/ChatPage.cc
index f64f332d..8981cb98 100644
--- a/src/ChatPage.cc
+++ b/src/ChatPage.cc
@@ -699,7 +699,7 @@ ChatPage::showQuickSwitcher()
{
if (quickSwitcher_.isNull()) {
quickSwitcher_ = QSharedPointer<QuickSwitcher>(
- new QuickSwitcher(this),
+ new QuickSwitcher(cache_, this),
[](QuickSwitcher *switcher) { switcher->deleteLater(); });
connect(quickSwitcher_.data(),
@@ -721,17 +721,7 @@ ChatPage::showQuickSwitcher()
quickSwitcherModal_->setColor(QColor(30, 30, 30, 170));
}
- try {
- std::map<QString, QString> rooms;
- auto info = cache_->roomInfo(false);
- for (auto it = info.begin(); it != info.end(); ++it)
- rooms.emplace(QString::fromStdString(it.value().name).trimmed(), it.key());
- quickSwitcher_->setRoomList(rooms);
- quickSwitcherModal_->show();
- } catch (const lmdb::error &e) {
- const auto err = QString::fromStdString(e.what());
- emit showNotification(QString("Failed to load room list: %1").arg(err));
- }
+ quickSwitcherModal_->show();
}
void
diff --git a/src/QuickSwitcher.cc b/src/QuickSwitcher.cc
index e8ef1cd9..d406a6de 100644
--- a/src/QuickSwitcher.cc
+++ b/src/QuickSwitcher.cc
@@ -20,6 +20,7 @@
#include <QStringListModel>
#include <QStyleOption>
#include <QTimer>
+#include <QtConcurrent>
#include "QuickSwitcher.h"
@@ -27,14 +28,6 @@ RoomSearchInput::RoomSearchInput(QWidget *parent)
: TextField(parent)
{}
-bool
-RoomSearchInput::focusNextPrevChild(bool next)
-{
- Q_UNUSED(next);
-
- return false;
-}
-
void
RoomSearchInput::keyPressEvent(QKeyEvent *event)
{
@@ -58,9 +51,11 @@ RoomSearchInput::hideEvent(QHideEvent *event)
TextField::hideEvent(event);
}
-QuickSwitcher::QuickSwitcher(QWidget *parent)
+QuickSwitcher::QuickSwitcher(QSharedPointer<Cache> cache, QWidget *parent)
: QWidget(parent)
+ , cache_{cache}
{
+ qRegisterMetaType<std::vector<RoomSearchResult>>();
setMaximumWidth(450);
QFont font;
@@ -68,89 +63,56 @@ QuickSwitcher::QuickSwitcher(QWidget *parent)
roomSearch_ = new RoomSearchInput(this);
roomSearch_->setFont(font);
- roomSearch_->setPlaceholderText(tr("Find a room..."));
-
- completer_ = new QCompleter();
- completer_->setCaseSensitivity(Qt::CaseInsensitive);
- completer_->setCompletionMode(QCompleter::PopupCompletion);
- completer_->setWidget(this);
+ roomSearch_->setPlaceholderText(tr("Search for a room..."));
topLayout_ = new QVBoxLayout(this);
topLayout_->addWidget(roomSearch_);
- connect(completer_, SIGNAL(highlighted(QString)), roomSearch_, SLOT(setText(QString)));
- connect(roomSearch_, &QLineEdit::textEdited, this, [this](const QString &prefix) {
- if (prefix.isEmpty()) {
- completer_->popup()->hide();
- selection_ = -1;
- return;
- }
-
- if (prefix != completer_->completionPrefix()) {
- completer_->setCompletionPrefix(prefix);
- selection_ = -1;
- }
-
- completer_->popup()->setWindowFlags(completer_->popup()->windowFlags() |
- Qt::ToolTip | Qt::NoDropShadowWindowHint);
- completer_->popup()->setAttribute(Qt::WA_ShowWithoutActivating);
- completer_->complete();
- });
+ connect(this,
+ &QuickSwitcher::queryResults,
+ this,
+ [this](const std::vector<RoomSearchResult> &rooms) {
+ auto pos = mapToGlobal(roomSearch_->geometry().bottomLeft());
- connect(roomSearch_, &RoomSearchInput::selectNextCompletion, this, [this]() {
- selection_ += 1;
+ popup_.setFixedWidth(width());
+ popup_.addRooms(rooms);
+ popup_.move(pos.x() - topLayout_->margin(), pos.y() + topLayout_->margin());
+ popup_.show();
+ });
- if (!completer_->setCurrentRow(selection_)) {
- selection_ = 0;
- completer_->setCurrentRow(selection_);
- }
-
- completer_->popup()->setCurrentIndex(completer_->currentIndex());
- });
-
- connect(roomSearch_, &RoomSearchInput::selectPreviousCompletion, this, [this]() {
- selection_ -= 1;
-
- if (!completer_->setCurrentRow(selection_)) {
- selection_ = completer_->completionCount() - 1;
- completer_->setCurrentRow(selection_);
+ connect(roomSearch_, &QLineEdit::textEdited, this, [this](const QString &query) {
+ if (query.isEmpty()) {
+ popup_.hide();
+ return;
}
- completer_->popup()->setCurrentIndex(completer_->currentIndex());
+ QtConcurrent::run([this, query = query.toLower()]() {
+ try {
+ emit queryResults(cache_->searchRooms(query.toStdString()));
+ } catch (const lmdb::error &e) {
+ qWarning() << "room search failed:" << e.what();
+ }
+ });
});
- connect(
- roomSearch_, &RoomSearchInput::hiding, this, [this]() { completer_->popup()->hide(); });
+ connect(roomSearch_,
+ &RoomSearchInput::selectNextCompletion,
+ &popup_,
+ &SuggestionsPopup::selectNextSuggestion);
+ connect(roomSearch_,
+ &RoomSearchInput::selectPreviousCompletion,
+ &popup_,
+ &SuggestionsPopup::selectPreviousSuggestion);
+ connect(&popup_, &SuggestionsPopup::itemSelected, this, &QuickSwitcher::roomSelected);
+ connect(roomSearch_, &RoomSearchInput::hiding, this, [this]() { popup_.hide(); });
connect(roomSearch_, &QLineEdit::returnPressed, this, [this]() {
emit closing();
-
- QString text("");
-
- if (selection_ == -1) {
- completer_->setCurrentRow(0);
- text = completer_->currentCompletion();
- } else {
- text = this->roomSearch_->text().trimmed();
- }
- emit roomSelected(rooms_[text]);
-
roomSearch_->clear();
+ popup_.selectHoveredSuggestion<RoomItem>();
});
}
void
-QuickSwitcher::setRoomList(const std::map<QString, QString> &rooms)
-{
- rooms_ = rooms;
-
- QStringList items;
- for (const auto &room : rooms)
- items << room.first;
-
- completer_->setModel(new QStringListModel(items));
-}
-
-void
QuickSwitcher::paintEvent(QPaintEvent *)
{
QStyleOption opt;
diff --git a/src/SuggestionsPopup.cpp b/src/SuggestionsPopup.cpp
index 49495e71..cb569ddf 100644
--- a/src/SuggestionsPopup.cpp
+++ b/src/SuggestionsPopup.cpp
@@ -1,7 +1,5 @@
#include "Avatar.h"
#include "AvatarProvider.h"
-#include "Cache.h"
-#include "ChatPage.h"
#include "Config.h"
#include "DropShadow.h"
#include "SuggestionsPopup.hpp"
@@ -15,10 +13,9 @@
constexpr int PopupHMargin = 5;
constexpr int PopupItemMargin = 4;
-PopupItem::PopupItem(QWidget *parent, const QString &user_id)
+PopupItem::PopupItem(QWidget *parent)
: QWidget(parent)
, avatar_{new Avatar(this)}
- , user_id_{user_id}
, hovering_{false}
{
setMouseTracking(true);
@@ -27,11 +24,39 @@ PopupItem::PopupItem(QWidget *parent, const QString &user_id)
topLayout_ = new QHBoxLayout(this);
topLayout_->setContentsMargins(
PopupHMargin, PopupItemMargin, PopupHMargin, PopupItemMargin);
+}
+
+void
+PopupItem::paintEvent(QPaintEvent *)
+{
+ QStyleOption opt;
+ opt.init(this);
+ QPainter p(this);
+ style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
+
+ if (underMouse() || hovering_)
+ p.fillRect(rect(), hoverColor_);
+}
+void
+PopupItem::mousePressEvent(QMouseEvent *event)
+{
+ if (event->buttons() != Qt::RightButton)
+ // TODO: should be abstracted.
+ emit clicked(
+ Cache::displayName(ChatPage::instance()->currentRoom(), selectedText()));
+
+ QWidget::mousePressEvent(event);
+}
+
+UserItem::UserItem(QWidget *parent, const QString &user_id)
+ : PopupItem(parent)
+ , userId_{user_id}
+{
QFont font;
font.setPixelSize(conf::popup::font);
- auto displayName = Cache::displayName(ChatPage::instance()->currentRoom(), user_id);
+ auto displayName = Cache::displayName(ChatPage::instance()->currentRoom(), userId_);
avatar_->setSize(conf::popup::avatar);
avatar_->setLetter(utils::firstChar(displayName));
@@ -47,30 +72,29 @@ PopupItem::PopupItem(QWidget *parent, const QString &user_id)
topLayout_->addWidget(userName_, 1);
AvatarProvider::resolve(ChatPage::instance()->currentRoom(),
- user_id,
+ userId_,
this,
[this](const QImage &img) { avatar_->setImage(img); });
}
-void
-PopupItem::paintEvent(QPaintEvent *)
+RoomItem::RoomItem(QWidget *parent, const RoomSearchResult &res)
+ : PopupItem(parent)
+ , roomId_{QString::fromStdString(res.room_id)}
{
- QStyleOption opt;
- opt.init(this);
- QPainter p(this);
- style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
+ auto name = QFontMetrics(QFont()).elidedText(
+ QString::fromStdString(res.info.name), Qt::ElideRight, parentWidget()->width() - 10);
- if (underMouse() || hovering_)
- p.fillRect(rect(), hoverColor_);
-}
+ avatar_->setSize(conf::popup::avatar + 6);
+ avatar_->setLetter(utils::firstChar(name));
-void
-PopupItem::mousePressEvent(QMouseEvent *event)
-{
- if (event->buttons() != Qt::RightButton)
- emit clicked(Cache::displayName(ChatPage::instance()->currentRoom(), user_id_));
+ roomName_ = new QLabel(name, this);
+ roomName_->setMargin(0);
- QWidget::mousePressEvent(event);
+ topLayout_->addWidget(avatar_);
+ topLayout_->addWidget(roomName_, 1);
+
+ if (!res.img.isNull())
+ avatar_->setImage(res.img);
}
SuggestionsPopup::SuggestionsPopup(QWidget *parent)
@@ -85,24 +109,41 @@ SuggestionsPopup::SuggestionsPopup(QWidget *parent)
}
void
-SuggestionsPopup::addUsers(const QVector<SearchResult> &users)
+SuggestionsPopup::addRooms(const std::vector<RoomSearchResult> &rooms)
{
- // Remove all items from the layout.
- QLayoutItem *item;
- while ((item = layout_->takeAt(0)) != 0) {
- delete item->widget();
- delete item;
+ removeItems();
+
+ if (rooms.empty()) {
+ hide();
+ return;
+ }
+
+ for (const auto &r : rooms) {
+ auto room = new RoomItem(this, r);
+ layout_->addWidget(room);
+ connect(room, &RoomItem::clicked, this, &SuggestionsPopup::itemSelected);
}
+ resetSelection();
+ adjustSize();
+
+ resize(geometry().width(), 40 * rooms.size());
+}
+
+void
+SuggestionsPopup::addUsers(const QVector<SearchResult> &users)
+{
+ removeItems();
+
if (users.isEmpty()) {
hide();
return;
}
for (const auto &u : users) {
- auto user = new PopupItem(this, u.user_id);
+ auto user = new UserItem(this, u.user_id);
layout_->addWidget(user);
- connect(user, &PopupItem::clicked, this, &SuggestionsPopup::itemSelected);
+ connect(user, &UserItem::clicked, this, &SuggestionsPopup::itemSelected);
}
resetSelection();
@@ -161,19 +202,6 @@ SuggestionsPopup::setHovering(int pos)
}
void
-SuggestionsPopup::selectHoveredSuggestion()
-{
- const auto item = layout_->itemAt(selectedItem_);
- if (!item)
- return;
-
- const auto &widget = qobject_cast<PopupItem *>(item->widget());
- emit itemSelected(Cache::displayName(ChatPage::instance()->currentRoom(), widget->user()));
-
- resetSelection();
-}
-
-void
SuggestionsPopup::paintEvent(QPaintEvent *)
{
QStyleOption opt;
diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc
index 1535f563..beae9aab 100644
--- a/src/TextInputWidget.cc
+++ b/src/TextInputWidget.cc
@@ -95,10 +95,9 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent)
&FilteredTextEdit::selectPreviousSuggestion,
&popup_,
&SuggestionsPopup::selectPreviousSuggestion);
- connect(this,
- &FilteredTextEdit::selectHoveredSuggestion,
- &popup_,
- &SuggestionsPopup::selectHoveredSuggestion);
+ connect(this, &FilteredTextEdit::selectHoveredSuggestion, this, [this]() {
+ popup_.selectHoveredSuggestion<UserItem>();
+ });
previewDialog_.hide();
}
@@ -459,7 +458,7 @@ TextInputWidget::TextInputWidget(QWidget *parent)
QtConcurrent::run([this, q = q.toLower().toStdString()]() {
try {
- emit input_->resultsRetrieved(cache_->getAutocompleteMatches(
+ emit input_->resultsRetrieved(cache_->searchUsers(
ChatPage::instance()->currentRoom().toStdString(), q));
} catch (const lmdb::error &e) {
std::cout << e.what() << '\n';
|