Working User completer

This commit is contained in:
Nicolas Werner 2020-11-20 02:38:08 +01:00
parent a3c4fece7e
commit add5903fb0
9 changed files with 83 additions and 38 deletions

View file

@ -492,6 +492,7 @@ qt5_wrap_cpp(MOC_HEADERS
src/ChatPage.h src/ChatPage.h
src/CommunitiesList.h src/CommunitiesList.h
src/CommunitiesListItem.h src/CommunitiesListItem.h
src/CompletionProxyModel.h
src/DeviceVerificationFlow.h src/DeviceVerificationFlow.h
src/InviteeItem.h src/InviteeItem.h
src/LoginPage.h src/LoginPage.h
@ -508,6 +509,7 @@ qt5_wrap_cpp(MOC_HEADERS
src/TrayIcon.h src/TrayIcon.h
src/UserInfoWidget.h src/UserInfoWidget.h
src/UserSettingsPage.h src/UserSettingsPage.h
src/UsersModel.h
src/WebRTCSession.h src/WebRTCSession.h
src/WelcomePage.h src/WelcomePage.h
src/popups/PopupItem.h src/popups/PopupItem.h

View file

@ -34,11 +34,17 @@ Popup {
onCompleterNameChanged: { onCompleterNameChanged: {
if (completerName) if (completerName)
completer = TimelineManager.timeline.input.completerFor(completerName); completer = TimelineManager.timeline.input.completerFor(completerName);
else
completer = undefined;
} }
padding: 0 padding: 0
onAboutToShow: currentIndex = -1 onAboutToShow: currentIndex = -1
Connections {
onTimelineChanged: completer = null
target: TimelineManager
}
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0

View file

@ -77,14 +77,13 @@ Rectangle {
onTextChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text) onTextChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
onCursorPositionChanged: { onCursorPositionChanged: {
TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text); TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
if (cursorPosition < completerTriggeredAt) { if (cursorPosition <= completerTriggeredAt) {
completerTriggeredAt = -1; completerTriggeredAt = -1;
popup.close(); popup.close();
} }
} }
onSelectionStartChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text) onSelectionStartChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
onSelectionEndChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text) onSelectionEndChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
// Ensure that we get escape key press events first.
Keys.onShortcutOverride: event.accepted = (completerTriggeredAt != -1 && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter)) Keys.onShortcutOverride: event.accepted = (completerTriggeredAt != -1 && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter))
Keys.onPressed: { Keys.onPressed: {
if (event.matches(StandardKey.Paste)) { if (event.matches(StandardKey.Paste)) {
@ -97,18 +96,20 @@ Rectangle {
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_N) { } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_N) {
textArea.text = TimelineManager.timeline.input.nextText(); textArea.text = TimelineManager.timeline.input.nextText();
} else if (event.key == Qt.Key_At) { } else if (event.key == Qt.Key_At) {
completerTriggeredAt = cursorPosition + 1; completerTriggeredAt = cursorPosition;
popup.completerName = "user"; popup.completerName = "user";
popup.open(); popup.open();
} else if (event.key == Qt.Key_Escape && popup.opened) { } else if (event.key == Qt.Key_Escape && popup.opened) {
completerTriggeredAt = -1; completerTriggeredAt = -1;
popup.completerName = "";
event.accepted = true; event.accepted = true;
popup.close(); popup.close();
} else if (event.matches(StandardKey.InsertParagraphSeparator) && popup.opened) { } else if (event.matches(StandardKey.InsertParagraphSeparator) && popup.opened) {
var currentCompletion = popup.currentCompletion(); var currentCompletion = popup.currentCompletion();
popup.completerName = "";
popup.close(); popup.close();
if (currentCompletion) { if (currentCompletion) {
textArea.remove(completerTriggeredAt - 1, cursorPosition); textArea.remove(completerTriggeredAt, cursorPosition);
textArea.insert(cursorPosition, currentCompletion); textArea.insert(cursorPosition, currentCompletion);
event.accepted = true; event.accepted = true;
return ; return ;
@ -129,6 +130,17 @@ Rectangle {
} }
} }
Connections {
onTimelineChanged: {
textArea.clear();
textArea.append(TimelineManager.timeline.input.text());
textArea.completerTriggeredAt = -1;
popup.completerName = "";
}
target: TimelineManager
}
// Ensure that we get escape key press events first.
Completer { Completer {
id: popup id: popup

View file

@ -1,20 +0,0 @@
#pragma once
// Class for showing a limited amount of completions at a time
#include <QSortFilterProxyModel>
class CompletionModel : public QSortFilterProxyModel
{
public:
CompletionModel(QAbstractItemModel *model, QObject *parent = nullptr)
: QSortFilterProxyModel(parent)
{
setSourceModel(model);
}
int rowCount(const QModelIndex &parent) const override
{
auto row_count = QSortFilterProxyModel::rowCount(parent);
return (row_count < 7) ? row_count : 7;
}
};

View file

@ -10,6 +10,6 @@ enum Roles
{ {
CompletionRole = Qt::UserRole * 2, // The string to replace the active completion CompletionRole = Qt::UserRole * 2, // The string to replace the active completion
SearchRole, // String completer uses for search SearchRole, // String completer uses for search
SearchRole2, // Secondary string completer uses for search
}; };
} }

View file

@ -4,17 +4,36 @@
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include "CompletionModelRoles.h"
class CompletionProxyModel : public QSortFilterProxyModel class CompletionProxyModel : public QSortFilterProxyModel
{ {
Q_OBJECT
public: public:
CompletionProxyModel(QAbstractItemModel *model, QObject *parent = nullptr) CompletionProxyModel(QAbstractItemModel *model, QObject *parent = nullptr)
: QSortFilterProxyModel(parent) : QSortFilterProxyModel(parent)
{ {
setSourceModel(model); setSourceModel(model);
} }
int rowCount(const QModelIndex &parent) const override
QHash<int, QByteArray> roleNames() const override
{
return this->sourceModel()->roleNames();
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{ {
auto row_count = QSortFilterProxyModel::rowCount(parent); auto row_count = QSortFilterProxyModel::rowCount(parent);
return (row_count < 7) ? row_count : 7; return (row_count < 7) ? row_count : 7;
} }
public slots:
QVariant completionAt(int i) const
{
if (i >= 0 && i < rowCount())
return data(index(i, 0), CompletionModel::CompletionRole);
else
return {};
}
}; };

View file

@ -3,12 +3,23 @@
#include "Cache.h" #include "Cache.h"
#include "CompletionModelRoles.h" #include "CompletionModelRoles.h"
#include <QPixmap>
UsersModel::UsersModel(const std::string &roomId, QObject *parent) UsersModel::UsersModel(const std::string &roomId, QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
, room_id(roomId)
{ {
roomMembers_ = cache::getMembers(roomId, 0, 9999); roomMembers_ = cache::roomMembers(roomId);
}
QHash<int, QByteArray>
UsersModel::roleNames() const
{
return {
{CompletionModel::CompletionRole, "completionRole"},
{CompletionModel::SearchRole, "searchRole"},
{CompletionModel::SearchRole2, "searchRole2"},
{Roles::DisplayName, "displayName"},
{Roles::AvatarUrl, "avatarUrl"},
};
} }
QVariant QVariant
@ -19,9 +30,14 @@ UsersModel::data(const QModelIndex &index, int role) const
case CompletionModel::CompletionRole: case CompletionModel::CompletionRole:
case CompletionModel::SearchRole: case CompletionModel::SearchRole:
case Qt::DisplayRole: case Qt::DisplayRole:
return roomMembers_[index.row()].display_name; case Roles::DisplayName:
case Avatar: return QString::fromStdString(
return roomMembers_[index.row()].avatar; cache::displayName(room_id, roomMembers_[index.row()]));
case CompletionModel::SearchRole2:
return QString::fromStdString(roomMembers_[index.row()]);
case Roles::AvatarUrl:
return cache::avatarUrl(QString::fromStdString(room_id),
QString::fromStdString(roomMembers_[index.row()]));
} }
} }
return {}; return {};

View file

@ -2,23 +2,25 @@
#include <QAbstractListModel> #include <QAbstractListModel>
class RoomMember;
class UsersModel : public QAbstractListModel class UsersModel : public QAbstractListModel
{ {
public: public:
enum Roles enum Roles
{ {
Avatar = Qt::UserRole // QImage avatar AvatarUrl = Qt::UserRole,
DisplayName,
}; };
UsersModel(const std::string &roomId, QObject *parent = nullptr); UsersModel(const std::string &roomId, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override int rowCount(const QModelIndex &parent = QModelIndex()) const override
{ {
return (parent == QModelIndex()) ? roomMembers_.size() : 0; (void)parent;
return roomMembers_.size();
} }
QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex &index, int role) const override;
private: private:
std::vector<RoomMember> roomMembers_; std::string room_id;
std::vector<std::string> roomMembers_;
}; };

View file

@ -14,12 +14,14 @@
#include "Cache.h" #include "Cache.h"
#include "CallManager.h" #include "CallManager.h"
#include "ChatPage.h" #include "ChatPage.h"
#include "CompletionProxyModel.h"
#include "Logging.h" #include "Logging.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "MatrixClient.h" #include "MatrixClient.h"
#include "Olm.h" #include "Olm.h"
#include "TimelineModel.h" #include "TimelineModel.h"
#include "UserSettingsPage.h" #include "UserSettingsPage.h"
#include "UsersModel.h"
#include "Utils.h" #include "Utils.h"
#include "dialogs/PlaceCall.h" #include "dialogs/PlaceCall.h"
#include "dialogs/PreviewUploadOverlay.h" #include "dialogs/PreviewUploadOverlay.h"
@ -166,6 +168,12 @@ InputBar::nextText()
QObject * QObject *
InputBar::completerFor(QString completerName) InputBar::completerFor(QString completerName)
{ {
if (completerName == "user") {
auto userModel = new UsersModel(room->roomId().toStdString());
auto proxy = new CompletionProxyModel(userModel);
userModel->setParent(proxy);
return proxy;
}
return nullptr; return nullptr;
} }