Fix reply not closing

fixes #124
This commit is contained in:
Nicolas Werner 2020-04-13 16:22:30 +02:00
parent 82ec022f9c
commit dcddea6fb8
14 changed files with 133 additions and 334 deletions

View File

@ -303,7 +303,6 @@ set(SRC_FILES
src/Utils.cpp src/Utils.cpp
src/WelcomePage.cpp src/WelcomePage.cpp
src/popups/PopupItem.cpp src/popups/PopupItem.cpp
src/popups/ReplyPopup.cpp
src/popups/SuggestionsPopup.cpp src/popups/SuggestionsPopup.cpp
src/popups/UserMentions.cpp src/popups/UserMentions.cpp
src/main.cpp src/main.cpp
@ -501,7 +500,6 @@ qt5_wrap_cpp(MOC_HEADERS
src/UserSettingsPage.h src/UserSettingsPage.h
src/WelcomePage.h src/WelcomePage.h
src/popups/PopupItem.h src/popups/PopupItem.h
src/popups/ReplyPopup.h
src/popups/SuggestionsPopup.h src/popups/SuggestionsPopup.h
src/popups/UserMentions.h src/popups/UserMentions.h
) )

View File

@ -285,7 +285,7 @@ Page {
id: replyPopup id: replyPopup
visible: timelineManager.replyingEvent && chat.model visible: chat.model && chat.model.reply
// Height of child, plus margins, plus border // Height of child, plus margins, plus border
height: replyPreview.height + 10 height: replyPreview.height + 10
color: colors.base color: colors.base
@ -300,7 +300,7 @@ Page {
anchors.rightMargin: 20 anchors.rightMargin: 20
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
modelData: chat.model ? chat.model.getDump(timelineManager.replyingEvent) : {} modelData: chat.model ? chat.model.getDump(chat.model.reply) : {}
userColor: timelineManager.userColor(modelData.userId, colors.window) userColor: timelineManager.userColor(modelData.userId, colors.window)
} }
@ -318,7 +318,7 @@ Page {
ToolTip.visible: closeReplyButton.hovered ToolTip.visible: closeReplyButton.hovered
ToolTip.text: qsTr("Close") ToolTip.text: qsTr("Close")
onClicked: timelineManager.closeReply() onClicked: chat.model.reply = undefined
} }
} }
} }

View File

@ -303,10 +303,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
text_input_, text_input_,
&TextInputWidget::uploadMedia, &TextInputWidget::uploadMedia,
this, this,
[this](QSharedPointer<QIODevice> dev, [this](QSharedPointer<QIODevice> dev, QString mimeClass, const QString &fn) {
QString mimeClass,
const QString &fn,
const std::optional<RelatedInfo> &related) {
if (!dev->open(QIODevice::ReadOnly)) { if (!dev->open(QIODevice::ReadOnly)) {
emit uploadFailed( emit uploadFailed(
QString("Error while reading media: %1").arg(dev->errorString())); QString("Error while reading media: %1").arg(dev->errorString()));
@ -358,8 +355,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
mime = mime.name(), mime = mime.name(),
size = payload.size(), size = payload.size(),
dimensions, dimensions,
blurhash, blurhash](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) {
related](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) {
if (err) { if (err) {
emit uploadFailed( emit uploadFailed(
tr("Failed to upload media. Please try again.")); tr("Failed to upload media. Please try again."));
@ -378,8 +374,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
mime, mime,
size, size,
dimensions, dimensions,
blurhash, blurhash);
related);
}); });
}); });
@ -398,8 +393,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
QString mime, QString mime,
qint64 dsize, qint64 dsize,
QSize dimensions, QSize dimensions,
QString blurhash, QString blurhash) {
const std::optional<RelatedInfo> &related) {
text_input_->hideUploadSpinner(); text_input_->hideUploadSpinner();
if (encryptedFile) if (encryptedFile)
@ -413,17 +407,16 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
mime, mime,
dsize, dsize,
dimensions, dimensions,
blurhash, blurhash);
related);
else if (mimeClass == "audio") else if (mimeClass == "audio")
view_manager_->queueAudioMessage( view_manager_->queueAudioMessage(
roomid, filename, encryptedFile, url, mime, dsize, related); roomid, filename, encryptedFile, url, mime, dsize);
else if (mimeClass == "video") else if (mimeClass == "video")
view_manager_->queueVideoMessage( view_manager_->queueVideoMessage(
roomid, filename, encryptedFile, url, mime, dsize, related); roomid, filename, encryptedFile, url, mime, dsize);
else else
view_manager_->queueFileMessage( view_manager_->queueFileMessage(
roomid, filename, encryptedFile, url, mime, dsize, related); roomid, filename, encryptedFile, url, mime, dsize);
}); });
connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar); connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar);
@ -548,14 +541,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
}); });
connect(this, &ChatPage::dropToLoginPageCb, this, &ChatPage::dropToLoginPage); connect(this, &ChatPage::dropToLoginPageCb, this, &ChatPage::dropToLoginPage);
connect(this, &ChatPage::messageReply, text_input_, &TextInputWidget::addReply);
connect(this, &ChatPage::messageReply, this, [this](const RelatedInfo &related) {
view_manager_->updateReplyingEvent(QString::fromStdString(related.related_event));
});
connect(view_manager_,
&TimelineViewManager::replyClosed,
text_input_,
&TextInputWidget::closeReplyPopup);
instance_ = this; instance_ = this;
} }
@ -596,6 +581,12 @@ ChatPage::resetUI()
showUnreadMessageNotification(0); showUnreadMessageNotification(0);
} }
void
ChatPage::focusMessageInput()
{
this->text_input_->focusLineEdit();
}
void void
ChatPage::deleteConfigs() ChatPage::deleteConfigs()
{ {

View File

@ -85,6 +85,7 @@ public:
//! Show the room/group list (if it was visible). //! Show the room/group list (if it was visible).
void showSideBars(); void showSideBars();
void initiateLogout(); void initiateLogout();
void focusMessageInput();
public slots: public slots:
void leaveRoom(const QString &room_id); void leaveRoom(const QString &room_id);
@ -99,8 +100,6 @@ signals:
void connectionLost(); void connectionLost();
void connectionRestored(); void connectionRestored();
void messageReply(const RelatedInfo &related);
void notificationsRetrieved(const mtx::responses::Notifications &); void notificationsRetrieved(const mtx::responses::Notifications &);
void highlightedNotifsRetrieved(const mtx::responses::Notifications &, void highlightedNotifsRetrieved(const mtx::responses::Notifications &,
const QPoint widgetPos); const QPoint widgetPos);
@ -114,8 +113,7 @@ signals:
const QString &mime, const QString &mime,
qint64 dsize, qint64 dsize,
const QSize &dimensions, const QSize &dimensions,
const QString &blurhash, const QString &blurhash);
const std::optional<RelatedInfo> &related);
void contentLoaded(); void contentLoaded();
void closing(); void closing();

View File

@ -46,7 +46,6 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent)
: QTextEdit{parent} : QTextEdit{parent}
, history_index_{0} , history_index_{0}
, suggestionsPopup_{parent} , suggestionsPopup_{parent}
, replyPopup_{parent}
, previewDialog_{parent} , previewDialog_{parent}
{ {
setFrameStyle(QFrame::NoFrame); setFrameStyle(QFrame::NoFrame);
@ -73,10 +72,6 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent)
&FilteredTextEdit::uploadData); &FilteredTextEdit::uploadData);
connect(this, &FilteredTextEdit::resultsRetrieved, this, &FilteredTextEdit::showResults); connect(this, &FilteredTextEdit::resultsRetrieved, this, &FilteredTextEdit::showResults);
connect(&replyPopup_, &ReplyPopup::userSelected, this, [](const QString &text) {
// TODO: Show user avatar window.
nhlog::ui()->info("User selected: " + text.toStdString());
});
connect( connect(
&suggestionsPopup_, &SuggestionsPopup::itemSelected, this, [this](const QString &text) { &suggestionsPopup_, &SuggestionsPopup::itemSelected, this, [this](const QString &text) {
suggestionsPopup_.hide(); suggestionsPopup_.hide();
@ -90,8 +85,6 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent)
cursor.insertText(text); cursor.insertText(text);
}); });
connect(&replyPopup_, &ReplyPopup::cancel, this, [this]() { closeReply(); });
// For cycling through the suggestions by hitting tab. // For cycling through the suggestions by hitting tab.
connect(this, connect(this,
&FilteredTextEdit::selectNextSuggestion, &FilteredTextEdit::selectNextSuggestion,
@ -174,17 +167,6 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event)
} }
} }
if (replyPopup_.isVisible()) {
switch (event->key()) {
case Qt::Key_Escape:
closeReply();
return;
default:
break;
}
}
switch (event->key()) { switch (event->key()) {
case Qt::Key_At: case Qt::Key_At:
atTriggerPosition_ = textCursor().position(); atTriggerPosition_ = textCursor().position();
@ -218,7 +200,6 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event)
if (!(event->modifiers() & Qt::ShiftModifier)) { if (!(event->modifiers() & Qt::ShiftModifier)) {
stopTyping(); stopTyping();
submit(); submit();
closeReply();
} else { } else {
QTextEdit::keyPressEvent(event); QTextEdit::keyPressEvent(event);
} }
@ -416,30 +397,17 @@ FilteredTextEdit::submit()
auto name = text.mid(1, command_end - 1); auto name = text.mid(1, command_end - 1);
auto args = text.mid(command_end + 1); auto args = text.mid(command_end + 1);
if (name.isEmpty() || name == "/") { if (name.isEmpty() || name == "/") {
message(args, related); message(args);
} else { } else {
command(name, args); command(name, args);
} }
} else { } else {
message(std::move(text), std::move(related)); message(std::move(text));
} }
related = {};
clear(); clear();
} }
void
FilteredTextEdit::showReplyPopup(const RelatedInfo &related_)
{
QPoint pos = viewport()->mapToGlobal(this->pos());
replyPopup_.setReplyContent(related_);
replyPopup_.move(pos.x(), pos.y() - replyPopup_.height() - 10);
replyPopup_.setFixedWidth(this->parentWidget()->width());
replyPopup_.show();
}
void void
FilteredTextEdit::textChanged() FilteredTextEdit::textChanged()
{ {
@ -456,9 +424,7 @@ FilteredTextEdit::uploadData(const QByteArray data,
emit startedUpload(); emit startedUpload();
emit media(buffer, mediaType, filename, related); emit media(buffer, mediaType, filename);
related = {};
closeReply();
} }
void void
@ -599,7 +565,7 @@ void
TextInputWidget::command(QString command, QString args) TextInputWidget::command(QString command, QString args)
{ {
if (command == "me") { if (command == "me") {
sendEmoteMessage(args, input_->related); sendEmoteMessage(args);
} else if (command == "join") { } else if (command == "join") {
sendJoinRoomRequest(args); sendJoinRoomRequest(args);
} else if (command == "invite") { } else if (command == "invite") {
@ -611,16 +577,14 @@ TextInputWidget::command(QString command, QString args)
} else if (command == "unban") { } else if (command == "unban") {
sendUnbanRoomRequest(args.section(' ', 0, 0), args.section(' ', 1, -1)); sendUnbanRoomRequest(args.section(' ', 0, 0), args.section(' ', 1, -1));
} else if (command == "shrug") { } else if (command == "shrug") {
sendTextMessage("¯\\_(ツ)_/¯", input_->related); sendTextMessage("¯\\_(ツ)_/¯");
} else if (command == "fliptable") { } else if (command == "fliptable") {
sendTextMessage("(╯°□°)╯︵ ┻━┻", input_->related); sendTextMessage("(╯°□°)╯︵ ┻━┻");
} else if (command == "unfliptable") { } else if (command == "unfliptable") {
sendTextMessage(" ┯━┯╭( º _ º╭)", input_->related); sendTextMessage(" ┯━┯╭( º _ º╭)");
} else if (command == "sovietflip") { } else if (command == "sovietflip") {
sendTextMessage("ノ┬─┬ノ ︵ ( \\o°o)\\", input_->related); sendTextMessage("ノ┬─┬ノ ︵ ( \\o°o)\\");
} }
input_->related = std::nullopt;
} }
void void
@ -640,9 +604,7 @@ TextInputWidget::openFileSelection()
QSharedPointer<QFile> file{new QFile{fileName, this}}; QSharedPointer<QFile> file{new QFile{fileName, this}};
emit uploadMedia(file, format, QFileInfo(fileName).fileName(), input_->related); emit uploadMedia(file, format, QFileInfo(fileName).fileName());
input_->related = {};
input_->closeReply();
showUploadSpinner(); showUploadSpinner();
} }
@ -687,16 +649,3 @@ TextInputWidget::paintEvent(QPaintEvent *)
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
} }
void
TextInputWidget::addReply(const RelatedInfo &related)
{
// input_->setText(QString("> %1: %2\n\n").arg(username).arg(msg));
input_->setFocus();
// input_->showReplyPopup(related);
auto cursor = input_->textCursor();
cursor.movePosition(QTextCursor::End);
input_->setTextCursor(cursor);
input_->related = related;
}

View File

@ -28,7 +28,6 @@
#include "dialogs/PreviewUploadOverlay.h" #include "dialogs/PreviewUploadOverlay.h"
#include "emoji/PickButton.h" #include "emoji/PickButton.h"
#include "popups/ReplyPopup.h"
#include "popups/SuggestionsPopup.h" #include "popups/SuggestionsPopup.h"
struct SearchResult; struct SearchResult;
@ -49,27 +48,15 @@ public:
QSize minimumSizeHint() const override; QSize minimumSizeHint() const override;
void submit(); void submit();
void showReplyPopup(const RelatedInfo &related_);
void closeReply()
{
replyPopup_.hide();
related = {};
}
// Used for replies
std::optional<RelatedInfo> related;
signals: signals:
void heightChanged(int height); void heightChanged(int height);
void startedTyping(); void startedTyping();
void stoppedTyping(); void stoppedTyping();
void startedUpload(); void startedUpload();
void message(QString, const std::optional<RelatedInfo> &); void message(QString msg);
void command(QString name, QString args); void command(QString name, QString args);
void media(QSharedPointer<QIODevice> data, void media(QSharedPointer<QIODevice> data, QString mimeClass, const QString &filename);
QString mimeClass,
const QString &filename,
const std::optional<RelatedInfo> &related);
//! Trigger the suggestion popup. //! Trigger the suggestion popup.
void showSuggestions(const QString &query); void showSuggestions(const QString &query);
@ -97,7 +84,6 @@ private:
QTimer *typingTimer_; QTimer *typingTimer_;
SuggestionsPopup suggestionsPopup_; SuggestionsPopup suggestionsPopup_;
ReplyPopup replyPopup_;
enum class AnchorType enum class AnchorType
{ {
@ -163,21 +149,18 @@ public slots:
void openFileSelection(); void openFileSelection();
void hideUploadSpinner(); void hideUploadSpinner();
void focusLineEdit() { input_->setFocus(); } void focusLineEdit() { input_->setFocus(); }
void addReply(const RelatedInfo &related);
void closeReplyPopup() { input_->closeReply(); }
private slots: private slots:
void addSelectedEmoji(const QString &emoji); void addSelectedEmoji(const QString &emoji);
signals: signals:
void sendTextMessage(const QString &msg, const std::optional<RelatedInfo> &related); void sendTextMessage(const QString &msg);
void sendEmoteMessage(QString msg, const std::optional<RelatedInfo> &related); void sendEmoteMessage(QString msg);
void heightChanged(int height); void heightChanged(int height);
void uploadMedia(const QSharedPointer<QIODevice> data, void uploadMedia(const QSharedPointer<QIODevice> data,
QString mimeClass, QString mimeClass,
const QString &filename, const QString &filename);
const std::optional<RelatedInfo> &related);
void sendJoinRoomRequest(const QString &room); void sendJoinRoomRequest(const QString &room);
void sendInviteRoomRequest(const QString &userid, const QString &reason); void sendInviteRoomRequest(const QString &userid, const QString &reason);

View File

@ -97,24 +97,24 @@ NotificationsManager::closeNotification(uint id)
void void
NotificationsManager::removeNotification(const QString &roomId, const QString &eventId) NotificationsManager::removeNotification(const QString &roomId, const QString &eventId)
{
roomEventId reId = {roomId, eventId};
for (auto elem = notificationIds.begin(); elem != notificationIds.end(); ++elem) {
if (elem.value().roomId != roomId)
continue;
roomEventId reId = {roomId, eventId}; // close all notifications matching the eventId or having a lower
for (auto elem = notificationIds.begin(); elem != notificationIds.end(); ++elem) { // notificationId
if (elem.value().roomId != roomId) // This relies on the notificationId not wrapping around. This allows for
continue; // approximately 2,147,483,647 notifications, so it is a bit unlikely.
// Otherwise we would need to store a 64bit counter instead.
closeNotification(elem.key());
// close all notifications matching the eventId or having a lower // FIXME: compare index of event id of the read receipt and the notification instead
// notificationId // of just the id to prevent read receipts of events without notification clearing
// This relies on the notificationId not wrapping around. This allows for // all notifications in that room!
// approximately 2,147,483,647 notifications, so it is a bit unlikely. if (elem.value() == reId)
// Otherwise we would need to store a 64bit counter instead. break;
closeNotification(elem.key());
// FIXME: compare index of event id of the read receipt and the notification instead of just
// the id to prevent read receipts of events without notification clearing all notifications
// in that room!
if (elem.value() == reId)
break;
} }
} }

View File

@ -67,4 +67,3 @@ void NotificationsManager::notificationClosed(uint, uint) {}
void void
NotificationsManager::removeNotification(const QString &roomId, const QString &eventId) NotificationsManager::removeNotification(const QString &roomId, const QString &eventId)
{} {}

View File

@ -1,103 +0,0 @@
#include <QLabel>
#include <QPaintEvent>
#include <QPainter>
#include <QStyleOption>
#include "../Config.h"
#include "../Utils.h"
#include "../ui/Avatar.h"
#include "../ui/DropShadow.h"
#include "../ui/TextLabel.h"
#include "PopupItem.h"
#include "ReplyPopup.h"
ReplyPopup::ReplyPopup(QWidget *parent)
: QWidget(parent)
, userItem_{nullptr}
, msgLabel_{nullptr}
, eventLabel_{nullptr}
{
setAttribute(Qt::WA_ShowWithoutActivating, true);
setWindowFlags(Qt::ToolTip | Qt::NoDropShadowWindowHint);
mainLayout_ = new QVBoxLayout(this);
mainLayout_->setMargin(0);
mainLayout_->setSpacing(0);
topLayout_ = new QHBoxLayout();
topLayout_->setSpacing(0);
topLayout_->setContentsMargins(13, 1, 13, 0);
userItem_ = new UserItem(this);
connect(userItem_, &UserItem::clicked, this, &ReplyPopup::userSelected);
topLayout_->addWidget(userItem_);
buttonLayout_ = new QHBoxLayout();
buttonLayout_->setSpacing(0);
buttonLayout_->setMargin(0);
topLayout_->addLayout(buttonLayout_);
QFont f;
f.setPointSizeF(f.pointSizeF());
const int fontHeight = QFontMetrics(f).height();
buttonSize_ = std::min(fontHeight, 20);
closeBtn_ = new FlatButton(this);
closeBtn_->setToolTip(tr("Logout"));
closeBtn_->setCornerRadius(buttonSize_ / 4);
closeBtn_->setText("X");
QIcon icon;
icon.addFile(":/icons/icons/ui/remove-symbol.png");
closeBtn_->setIcon(icon);
closeBtn_->setIconSize(QSize(buttonSize_, buttonSize_));
connect(closeBtn_, &FlatButton::clicked, this, [this]() { emit cancel(); });
buttonLayout_->addWidget(closeBtn_);
topLayout_->addLayout(buttonLayout_);
mainLayout_->addLayout(topLayout_);
msgLabel_ = new TextLabel(this);
msgLabel_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
mainLayout_->addWidget(msgLabel_);
eventLabel_ = new QLabel(this);
mainLayout_->addWidget(eventLabel_);
setLayout(mainLayout_);
}
void
ReplyPopup::setReplyContent(const RelatedInfo &related)
{
// Update the current widget with the new data.
userItem_->updateItem(related.quoted_user);
msgLabel_->setText(utils::getFormattedQuoteBody(related, "")
.replace("<mx-reply>", "")
.replace("</mx-reply>", ""));
// eventLabel_->setText(srcEvent);
adjustSize();
}
void
ReplyPopup::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
void
ReplyPopup::mousePressEvent(QMouseEvent *event)
{
if (event->buttons() != Qt::RightButton) {
emit clicked(eventLabel_->text());
}
QWidget::mousePressEvent(event);
}

View File

@ -1,44 +0,0 @@
#pragma once
#include <QHBoxLayout>
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>
#include "../ui/FlatButton.h"
#include "../ui/TextLabel.h"
struct RelatedInfo;
class UserItem;
class ReplyPopup : public QWidget
{
Q_OBJECT
public:
explicit ReplyPopup(QWidget *parent = nullptr);
public slots:
void setReplyContent(const RelatedInfo &related);
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
signals:
void userSelected(const QString &user);
void clicked(const QString &text);
void cancel();
private:
QHBoxLayout *topLayout_;
QVBoxLayout *mainLayout_;
QHBoxLayout *buttonLayout_;
UserItem *userItem_;
FlatButton *closeBtn_;
TextLabel *msgLabel_;
QLabel *eventLabel_;
int buttonSize_;
};

View File

@ -800,6 +800,16 @@ TimelineModel::decryptEvent(const mtx::events::EncryptedEvent<mtx::events::msg::
void void
TimelineModel::replyAction(QString id) TimelineModel::replyAction(QString id)
{ {
setReply(id);
ChatPage::instance()->focusMessageInput();
}
RelatedInfo
TimelineModel::relatedInfo(QString id)
{
if (!events.contains(id))
return {};
auto event = events.value(id); auto event = events.value(id);
if (auto e = if (auto e =
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) { std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
@ -815,10 +825,9 @@ TimelineModel::replyAction(QString id)
related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(event); related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(event);
related.quoted_formatted_body.remove(QRegularExpression( related.quoted_formatted_body.remove(QRegularExpression(
"<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption)); "<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption));
nhlog::ui()->debug("after replacement: {}", related.quoted_body.toStdString());
related.room = room_id_; related.room = room_id_;
ChatPage::instance()->messageReply(related); return related;
} }
void void

View File

@ -18,6 +18,7 @@ struct Timeline;
struct Messages; struct Messages;
struct ClaimKeys; struct ClaimKeys;
} }
struct RelatedInfo;
namespace qml_mtx_events { namespace qml_mtx_events {
Q_NAMESPACE Q_NAMESPACE
@ -124,6 +125,7 @@ class TimelineModel : public QAbstractListModel
int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY
typingUsersChanged) typingUsersChanged)
Q_PROPERTY(QString reply READ reply WRITE setReply NOTIFY replyChanged RESET resetReply)
public: public:
explicit TimelineModel(TimelineViewManager *manager, explicit TimelineModel(TimelineViewManager *manager,
@ -191,6 +193,7 @@ public:
void addEvents(const mtx::responses::Timeline &events); void addEvents(const mtx::responses::Timeline &events);
template<class T> template<class T>
void sendMessage(const T &msg); void sendMessage(const T &msg);
RelatedInfo relatedInfo(QString id);
public slots: public slots:
void setCurrentIndex(int index); void setCurrentIndex(int index);
@ -206,6 +209,22 @@ public slots:
} }
std::vector<QString> typingUsers() const { return typingUsers_; } std::vector<QString> typingUsers() const { return typingUsers_; }
QString reply() const { return reply_; }
void setReply(QString newReply)
{
if (reply_ != newReply) {
reply_ = newReply;
emit replyChanged(reply_);
}
}
void resetReply()
{
if (!reply_.isEmpty()) {
reply_ = "";
emit replyChanged(reply_);
}
}
private slots: private slots:
// Add old events at the top of the timeline. // Add old events at the top of the timeline.
void addBackwardsEvents(const mtx::responses::Messages &msgs); void addBackwardsEvents(const mtx::responses::Messages &msgs);
@ -225,6 +244,7 @@ signals:
void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo); void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo);
void eventFetched(QString requestingEvent, mtx::events::collections::TimelineEvents event); void eventFetched(QString requestingEvent, mtx::events::collections::TimelineEvents event);
void typingUsersChanged(std::vector<QString> users); void typingUsersChanged(std::vector<QString> users);
void replyChanged(QString reply);
private: private:
DecryptionResult decryptEvent( DecryptionResult decryptEvent(
@ -254,6 +274,7 @@ private:
bool isProcessingPending = false; bool isProcessingPending = false;
QString currentId; QString currentId;
QString reply_;
std::vector<QString> typingUsers_; std::vector<QString> typingUsers_;
TimelineViewManager *manager_; TimelineViewManager *manager_;

View File

@ -188,8 +188,11 @@ TimelineViewManager::initWithMessages(const std::map<QString, mtx::responses::Ti
} }
void void
TimelineViewManager::queueTextMessage(const QString &msg, const std::optional<RelatedInfo> &related) TimelineViewManager::queueTextMessage(const QString &msg)
{ {
if (!timeline_)
return;
mtx::events::msg::Text text = {}; mtx::events::msg::Text text = {};
text.body = msg.trimmed().toStdString(); text.body = msg.trimmed().toStdString();
@ -203,13 +206,15 @@ TimelineViewManager::queueTextMessage(const QString &msg, const std::optional<Re
text.format = "org.matrix.custom.html"; text.format = "org.matrix.custom.html";
} }
if (related) { if (!timeline_->reply().isEmpty()) {
auto related = timeline_->relatedInfo(timeline_->reply());
QString body; QString body;
bool firstLine = true; bool firstLine = true;
for (const auto &line : related->quoted_body.split("\n")) { for (const auto &line : related.quoted_body.split("\n")) {
if (firstLine) { if (firstLine) {
firstLine = false; firstLine = false;
body = QString("> <%1> %2\n").arg(related->quoted_user).arg(line); body = QString("> <%1> %2\n").arg(related.quoted_user).arg(line);
} else { } else {
body = QString("%1\n> %2\n").arg(body).arg(line); body = QString("%1\n> %2\n").arg(body).arg(line);
} }
@ -221,17 +226,17 @@ TimelineViewManager::queueTextMessage(const QString &msg, const std::optional<Re
text.format = "org.matrix.custom.html"; text.format = "org.matrix.custom.html";
if (settings->isMarkdownEnabled()) if (settings->isMarkdownEnabled())
text.formatted_body = text.formatted_body =
utils::getFormattedQuoteBody(*related, utils::markdownToHtml(msg)) utils::getFormattedQuoteBody(related, utils::markdownToHtml(msg))
.toStdString(); .toStdString();
else else
text.formatted_body = text.formatted_body =
utils::getFormattedQuoteBody(*related, msg.toHtmlEscaped()).toStdString(); utils::getFormattedQuoteBody(related, msg.toHtmlEscaped()).toStdString();
text.relates_to.in_reply_to.event_id = related->related_event; text.relates_to.in_reply_to.event_id = related.related_event;
timeline_->resetReply();
} }
if (timeline_) timeline_->sendMessage(text);
timeline_->sendMessage(text);
} }
void void
@ -247,6 +252,11 @@ TimelineViewManager::queueEmoteMessage(const QString &msg)
emote.format = "org.matrix.custom.html"; emote.format = "org.matrix.custom.html";
} }
if (!timeline_->reply().isEmpty()) {
emote.relates_to.in_reply_to.event_id = timeline_->reply().toStdString();
timeline_->resetReply();
}
if (timeline_) if (timeline_)
timeline_->sendMessage(emote); timeline_->sendMessage(emote);
} }
@ -259,8 +269,7 @@ TimelineViewManager::queueImageMessage(const QString &roomid,
const QString &mime, const QString &mime,
uint64_t dsize, uint64_t dsize,
const QSize &dimensions, const QSize &dimensions,
const QString &blurhash, const QString &blurhash)
const std::optional<RelatedInfo> &related)
{ {
mtx::events::msg::Image image; mtx::events::msg::Image image;
image.info.mimetype = mime.toStdString(); image.info.mimetype = mime.toStdString();
@ -272,10 +281,13 @@ TimelineViewManager::queueImageMessage(const QString &roomid,
image.info.w = dimensions.width(); image.info.w = dimensions.width();
image.file = file; image.file = file;
if (related) auto model = models.value(roomid);
image.relates_to.in_reply_to.event_id = related->related_event; if (!model->reply().isEmpty()) {
image.relates_to.in_reply_to.event_id = model->reply().toStdString();
model->resetReply();
}
models.value(roomid)->sendMessage(image); model->sendMessage(image);
} }
void void
@ -285,8 +297,7 @@ TimelineViewManager::queueFileMessage(
const std::optional<mtx::crypto::EncryptedFile> &encryptedFile, const std::optional<mtx::crypto::EncryptedFile> &encryptedFile,
const QString &url, const QString &url,
const QString &mime, const QString &mime,
uint64_t dsize, uint64_t dsize)
const std::optional<RelatedInfo> &related)
{ {
mtx::events::msg::File file; mtx::events::msg::File file;
file.info.mimetype = mime.toStdString(); file.info.mimetype = mime.toStdString();
@ -295,10 +306,13 @@ TimelineViewManager::queueFileMessage(
file.url = url.toStdString(); file.url = url.toStdString();
file.file = encryptedFile; file.file = encryptedFile;
if (related) auto model = models.value(roomid);
file.relates_to.in_reply_to.event_id = related->related_event; if (!model->reply().isEmpty()) {
file.relates_to.in_reply_to.event_id = model->reply().toStdString();
model->resetReply();
}
models.value(roomid)->sendMessage(file); model->sendMessage(file);
} }
void void
@ -307,8 +321,7 @@ TimelineViewManager::queueAudioMessage(const QString &roomid,
const std::optional<mtx::crypto::EncryptedFile> &file, const std::optional<mtx::crypto::EncryptedFile> &file,
const QString &url, const QString &url,
const QString &mime, const QString &mime,
uint64_t dsize, uint64_t dsize)
const std::optional<RelatedInfo> &related)
{ {
mtx::events::msg::Audio audio; mtx::events::msg::Audio audio;
audio.info.mimetype = mime.toStdString(); audio.info.mimetype = mime.toStdString();
@ -317,10 +330,13 @@ TimelineViewManager::queueAudioMessage(const QString &roomid,
audio.url = url.toStdString(); audio.url = url.toStdString();
audio.file = file; audio.file = file;
if (related) auto model = models.value(roomid);
audio.relates_to.in_reply_to.event_id = related->related_event; if (!model->reply().isEmpty()) {
audio.relates_to.in_reply_to.event_id = model->reply().toStdString();
model->resetReply();
}
models.value(roomid)->sendMessage(audio); model->sendMessage(audio);
} }
void void
@ -329,8 +345,7 @@ TimelineViewManager::queueVideoMessage(const QString &roomid,
const std::optional<mtx::crypto::EncryptedFile> &file, const std::optional<mtx::crypto::EncryptedFile> &file,
const QString &url, const QString &url,
const QString &mime, const QString &mime,
uint64_t dsize, uint64_t dsize)
const std::optional<RelatedInfo> &related)
{ {
mtx::events::msg::Video video; mtx::events::msg::Video video;
video.info.mimetype = mime.toStdString(); video.info.mimetype = mime.toStdString();
@ -339,8 +354,11 @@ TimelineViewManager::queueVideoMessage(const QString &roomid,
video.url = url.toStdString(); video.url = url.toStdString();
video.file = file; video.file = file;
if (related) auto model = models.value(roomid);
video.relates_to.in_reply_to.event_id = related->related_event; if (!model->reply().isEmpty()) {
video.relates_to.in_reply_to.event_id = model->reply().toStdString();
model->resetReply();
}
models.value(roomid)->sendMessage(video); model->sendMessage(video);
} }

View File

@ -26,8 +26,6 @@ class TimelineViewManager : public QObject
TimelineModel *timeline MEMBER timeline_ READ activeTimeline NOTIFY activeTimelineChanged) TimelineModel *timeline MEMBER timeline_ READ activeTimeline NOTIFY activeTimelineChanged)
Q_PROPERTY( Q_PROPERTY(
bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged) bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
Q_PROPERTY(QString replyingEvent READ getReplyingEvent WRITE updateReplyingEvent NOTIFY
replyingEventChanged)
public: public:
TimelineViewManager(QSharedPointer<UserSettings> userSettings, QWidget *parent = nullptr); TimelineViewManager(QSharedPointer<UserSettings> userSettings, QWidget *parent = nullptr);
@ -52,26 +50,13 @@ signals:
void replyClosed(); void replyClosed();
public slots: public slots:
void updateReplyingEvent(const QString &replyingEvent)
{
if (this->replyingEvent_ != replyingEvent) {
this->replyingEvent_ = replyingEvent;
emit replyingEventChanged(replyingEvent_);
}
}
void closeReply()
{
this->updateReplyingEvent(nullptr);
emit replyClosed();
}
QString getReplyingEvent() const { return replyingEvent_; }
void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids); void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
void initWithMessages(const std::map<QString, mtx::responses::Timeline> &msgs); void initWithMessages(const std::map<QString, mtx::responses::Timeline> &msgs);
void setHistoryView(const QString &room_id); void setHistoryView(const QString &room_id);
void updateColorPalette(); void updateColorPalette();
void queueTextMessage(const QString &msg, const std::optional<RelatedInfo> &related); void queueTextMessage(const QString &msg);
void queueEmoteMessage(const QString &msg); void queueEmoteMessage(const QString &msg);
void queueImageMessage(const QString &roomid, void queueImageMessage(const QString &roomid,
const QString &filename, const QString &filename,
@ -80,29 +65,25 @@ public slots:
const QString &mime, const QString &mime,
uint64_t dsize, uint64_t dsize,
const QSize &dimensions, const QSize &dimensions,
const QString &blurhash, const QString &blurhash);
const std::optional<RelatedInfo> &related);
void queueFileMessage(const QString &roomid, void queueFileMessage(const QString &roomid,
const QString &filename, const QString &filename,
const std::optional<mtx::crypto::EncryptedFile> &file, const std::optional<mtx::crypto::EncryptedFile> &file,
const QString &url, const QString &url,
const QString &mime, const QString &mime,
uint64_t dsize, uint64_t dsize);
const std::optional<RelatedInfo> &related);
void queueAudioMessage(const QString &roomid, void queueAudioMessage(const QString &roomid,
const QString &filename, const QString &filename,
const std::optional<mtx::crypto::EncryptedFile> &file, const std::optional<mtx::crypto::EncryptedFile> &file,
const QString &url, const QString &url,
const QString &mime, const QString &mime,
uint64_t dsize, uint64_t dsize);
const std::optional<RelatedInfo> &related);
void queueVideoMessage(const QString &roomid, void queueVideoMessage(const QString &roomid,
const QString &filename, const QString &filename,
const std::optional<mtx::crypto::EncryptedFile> &file, const std::optional<mtx::crypto::EncryptedFile> &file,
const QString &url, const QString &url,
const QString &mime, const QString &mime,
uint64_t dsize, uint64_t dsize);
const std::optional<RelatedInfo> &related);
private: private:
#ifdef USE_QUICK_VIEW #ifdef USE_QUICK_VIEW
@ -119,7 +100,6 @@ private:
QHash<QString, QSharedPointer<TimelineModel>> models; QHash<QString, QSharedPointer<TimelineModel>> models;
TimelineModel *timeline_ = nullptr; TimelineModel *timeline_ = nullptr;
bool isInitialSync_ = true; bool isInitialSync_ = true;
QString replyingEvent_;
QSharedPointer<UserSettings> settings; QSharedPointer<UserSettings> settings;
QHash<QString, QColor> userColors; QHash<QString, QColor> userColors;