mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 11:00:48 +03:00
Further Improve Reply Functionality
Quoted replies now include matrix.to links for the event and the user. UI Rendering has been (slightly) improved... still very WIP. Restructured the reply structure in the code for future usability improvements.
This commit is contained in:
parent
9f310fed09
commit
129beb57c9
15 changed files with 177 additions and 74 deletions
|
@ -265,9 +265,9 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||
SLOT(queueTextMessage(const QString &)));
|
||||
|
||||
connect(text_input_,
|
||||
SIGNAL(sendReplyMessage(const QString &, const QString &)),
|
||||
SIGNAL(sendReplyMessage(const QString &, const RelatedInfo &)),
|
||||
view_manager_,
|
||||
SLOT(queueReplyMessage(const QString &, const QString &)));
|
||||
SLOT(queueReplyMessage(const QString &, const RelatedInfo &)));
|
||||
|
||||
connect(text_input_,
|
||||
SIGNAL(sendEmoteMessage(const QString &)),
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "Cache.h"
|
||||
#include "CommunitiesList.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "Utils.h"
|
||||
#include "notifications/Manager.h"
|
||||
|
||||
class OverlayModal;
|
||||
|
@ -83,9 +84,7 @@ signals:
|
|||
void connectionLost();
|
||||
void connectionRestored();
|
||||
|
||||
void messageReply(const QString &username,
|
||||
const QString &msg,
|
||||
const QString &related_event);
|
||||
void messageReply(const RelatedInfo &related);
|
||||
|
||||
void notificationsRetrieved(const mtx::responses::Notifications &);
|
||||
|
||||
|
|
|
@ -93,6 +93,8 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent)
|
|||
cursor.insertText(text);
|
||||
});
|
||||
|
||||
connect(&replyPopup_, &ReplyPopup::cancel, this, [this]() { closeReply(); });
|
||||
|
||||
// For cycling through the suggestions by hitting tab.
|
||||
connect(this,
|
||||
&FilteredTextEdit::selectNextSuggestion,
|
||||
|
@ -219,6 +221,7 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event)
|
|||
if (!(event->modifiers() & Qt::ShiftModifier)) {
|
||||
stopTyping();
|
||||
submit();
|
||||
closeReply();
|
||||
} else {
|
||||
QTextEdit::keyPressEvent(event);
|
||||
}
|
||||
|
@ -415,8 +418,8 @@ FilteredTextEdit::submit()
|
|||
auto name = text.mid(1, command_end - 1);
|
||||
auto args = text.mid(command_end + 1);
|
||||
if (name.isEmpty() || name == "/") {
|
||||
if (!related_event_.isEmpty()) {
|
||||
reply(args, related_event_);
|
||||
if (!related_.related_event.empty()) {
|
||||
reply(args, related_);
|
||||
} else {
|
||||
message(args);
|
||||
}
|
||||
|
@ -424,14 +427,14 @@ FilteredTextEdit::submit()
|
|||
command(name, args);
|
||||
}
|
||||
} else {
|
||||
if (!related_event_.isEmpty()) {
|
||||
reply(std::move(text), std::move(related_event_));
|
||||
if (!related_.related_event.empty()) {
|
||||
reply(std::move(text), std::move(related_));
|
||||
} else {
|
||||
message(std::move(text));
|
||||
}
|
||||
}
|
||||
|
||||
related_event_ = "";
|
||||
related_ = {};
|
||||
|
||||
clear();
|
||||
}
|
||||
|
@ -439,16 +442,8 @@ FilteredTextEdit::submit()
|
|||
void
|
||||
FilteredTextEdit::showReplyPopup(const QString &user, const QString &msg, const QString &event_id)
|
||||
{
|
||||
QPoint pos;
|
||||
QPoint pos = viewport()->mapToGlobal(this->pos());
|
||||
|
||||
if (isAnchorValid()) {
|
||||
auto cursor = textCursor();
|
||||
cursor.setPosition(atTriggerPosition_);
|
||||
pos = viewport()->mapToGlobal(cursorRect(cursor).topLeft());
|
||||
} else {
|
||||
auto rect = cursorRect();
|
||||
pos = viewport()->mapToGlobal(rect.topLeft());
|
||||
}
|
||||
replyPopup_.setReplyContent(user, msg, event_id);
|
||||
replyPopup_.move(pos.x(), pos.y() - replyPopup_.height() - 10);
|
||||
replyPopup_.show();
|
||||
|
@ -699,14 +694,15 @@ TextInputWidget::paintEvent(QPaintEvent *)
|
|||
}
|
||||
|
||||
void
|
||||
TextInputWidget::addReply(const QString &username, const QString &msg, const QString &replied_event)
|
||||
TextInputWidget::addReply(const RelatedInfo &related)
|
||||
{
|
||||
// input_->setText(QString("> %1: %2\n\n").arg(username).arg(msg));
|
||||
input_->setFocus();
|
||||
|
||||
input_->showReplyPopup(username, msg, replied_event);
|
||||
input_->showReplyPopup(
|
||||
related.quoted_user, related.quoted_body, QString::fromStdString(related.related_event));
|
||||
auto cursor = input_->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
input_->setTextCursor(cursor);
|
||||
input_->setRelatedEvent(replied_event);
|
||||
input_->setRelated(related);
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <QTextEdit>
|
||||
#include <QWidget>
|
||||
|
||||
#include "Utils.h"
|
||||
#include "dialogs/PreviewUploadOverlay.h"
|
||||
#include "emoji/PickButton.h"
|
||||
#include "popups/ReplyPopup.h"
|
||||
|
@ -55,7 +56,7 @@ public:
|
|||
QSize minimumSizeHint() const override;
|
||||
|
||||
void submit();
|
||||
void setRelatedEvent(const QString &event) { related_event_ = event; }
|
||||
void setRelated(const RelatedInfo &related) { related_ = related; }
|
||||
void showReplyPopup(const QString &user, const QString &msg, const QString &event_id);
|
||||
|
||||
signals:
|
||||
|
@ -64,7 +65,7 @@ signals:
|
|||
void stoppedTyping();
|
||||
void startedUpload();
|
||||
void message(QString);
|
||||
void reply(QString, QString);
|
||||
void reply(QString, const RelatedInfo &);
|
||||
void command(QString name, QString args);
|
||||
void image(QSharedPointer<QIODevice> data, const QString &filename);
|
||||
void audio(QSharedPointer<QIODevice> data, const QString &filename);
|
||||
|
@ -100,7 +101,7 @@ private:
|
|||
ReplyPopup replyPopup_;
|
||||
|
||||
// Used for replies
|
||||
QString related_event_;
|
||||
RelatedInfo related_;
|
||||
|
||||
enum class AnchorType
|
||||
{
|
||||
|
@ -113,7 +114,11 @@ private:
|
|||
int anchorWidth(AnchorType anchor) { return static_cast<int>(anchor); }
|
||||
|
||||
void closeSuggestions() { suggestionsPopup_.hide(); }
|
||||
void closeReply() { replyPopup_.hide(); }
|
||||
void closeReply()
|
||||
{
|
||||
replyPopup_.hide();
|
||||
related_ = {};
|
||||
}
|
||||
void resetAnchor() { atTriggerPosition_ = -1; }
|
||||
bool isAnchorValid() { return atTriggerPosition_ != -1; }
|
||||
bool hasAnchor(int pos, AnchorType anchor)
|
||||
|
@ -167,14 +172,14 @@ public slots:
|
|||
void openFileSelection();
|
||||
void hideUploadSpinner();
|
||||
void focusLineEdit() { input_->setFocus(); }
|
||||
void addReply(const QString &username, const QString &msg, const QString &related_event);
|
||||
void addReply(const RelatedInfo &related);
|
||||
|
||||
private slots:
|
||||
void addSelectedEmoji(const QString &emoji);
|
||||
|
||||
signals:
|
||||
void sendTextMessage(QString msg);
|
||||
void sendReplyMessage(QString msg, QString event_id);
|
||||
void sendReplyMessage(QString msg, const RelatedInfo &related);
|
||||
void sendEmoteMessage(QString msg);
|
||||
void heightChanged(int height);
|
||||
|
||||
|
|
|
@ -314,6 +314,20 @@ utils::markdownToHtml(const QString &text)
|
|||
return result;
|
||||
}
|
||||
|
||||
QString
|
||||
utils::getFormattedQuoteBody(const RelatedInfo &related, const QString &html)
|
||||
{
|
||||
return QString("<mx-reply><blockquote><a "
|
||||
"href=\"https://matrix.to/#/!%1\">In reply "
|
||||
"to</a><a href=\"https://matrix.to/#/%2\">%3</a><br "
|
||||
"/>%4</blockquote></mx-reply>")
|
||||
.arg(QString::fromStdString(related.related_event),
|
||||
related.quoted_user,
|
||||
related.quoted_user,
|
||||
related.quoted_body) +
|
||||
html;
|
||||
}
|
||||
|
||||
QString
|
||||
utils::linkColor()
|
||||
{
|
||||
|
|
13
src/Utils.h
13
src/Utils.h
|
@ -18,6 +18,15 @@
|
|||
|
||||
class QComboBox;
|
||||
|
||||
// Contains information about related events for
|
||||
// outgoing messages
|
||||
struct RelatedInfo
|
||||
{
|
||||
QString quoted_body;
|
||||
std::string related_event;
|
||||
QString quoted_user;
|
||||
};
|
||||
|
||||
namespace utils {
|
||||
|
||||
using TimelineEvent = mtx::events::collections::TimelineEvents;
|
||||
|
@ -225,6 +234,10 @@ linkifyMessage(const QString &body);
|
|||
QString
|
||||
markdownToHtml(const QString &text);
|
||||
|
||||
//! Generate a Rich Reply quote message
|
||||
QString
|
||||
getFormattedQuoteBody(const RelatedInfo &related, const QString &html);
|
||||
|
||||
//! Retrieve the color of the links based on the current theme.
|
||||
QString
|
||||
linkColor();
|
||||
|
|
|
@ -36,6 +36,16 @@ PopupItem::paintEvent(QPaintEvent *)
|
|||
p.fillRect(rect(), hoverColor_);
|
||||
}
|
||||
|
||||
UserItem::UserItem(QWidget *parent)
|
||||
: PopupItem(parent)
|
||||
{
|
||||
userName_ = new QLabel("Placeholder", this);
|
||||
avatar_->setSize(conf::popup::avatar);
|
||||
avatar_->setLetter("P");
|
||||
topLayout_->addWidget(avatar_);
|
||||
topLayout_->addWidget(userName_, 1);
|
||||
}
|
||||
|
||||
UserItem::UserItem(QWidget *parent, const QString &user_id)
|
||||
: PopupItem(parent)
|
||||
, userId_{user_id}
|
||||
|
|
|
@ -50,6 +50,7 @@ class UserItem : public PopupItem
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
UserItem(QWidget *parent);
|
||||
UserItem(QWidget *parent, const QString &user_id);
|
||||
QString selectedText() const { return userId_; }
|
||||
void updateItem(const QString &user_id);
|
||||
|
|
|
@ -11,41 +11,69 @@
|
|||
|
||||
ReplyPopup::ReplyPopup(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, userItem_{0}
|
||||
, msgLabel_{0}
|
||||
, eventLabel_{0}
|
||||
{
|
||||
setAttribute(Qt::WA_ShowWithoutActivating, true);
|
||||
setWindowFlags(Qt::ToolTip | Qt::NoDropShadowWindowHint);
|
||||
|
||||
layout_ = new QVBoxLayout(this);
|
||||
layout_->setMargin(0);
|
||||
layout_->setSpacing(0);
|
||||
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_ / 2);
|
||||
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 QLabel(this);
|
||||
mainLayout_->addWidget(msgLabel_);
|
||||
eventLabel_ = new QLabel(this);
|
||||
mainLayout_->addWidget(eventLabel_);
|
||||
|
||||
setLayout(mainLayout_);
|
||||
}
|
||||
|
||||
void
|
||||
ReplyPopup::setReplyContent(const QString &user, const QString &msg, const QString &srcEvent)
|
||||
{
|
||||
QLayoutItem *child;
|
||||
while ((child = layout_->takeAt(0)) != 0) {
|
||||
delete child->widget();
|
||||
delete child;
|
||||
}
|
||||
// Create a new widget if there isn't already one in that
|
||||
// layout position.
|
||||
// if (!item) {
|
||||
auto userItem = new UserItem(this, user);
|
||||
auto *text = new QLabel(this);
|
||||
text->setText(msg);
|
||||
auto *event = new QLabel(this);
|
||||
event->setText(srcEvent);
|
||||
connect(userItem, &UserItem::clicked, this, &ReplyPopup::userSelected);
|
||||
layout_->addWidget(userItem);
|
||||
layout_->addWidget(text);
|
||||
layout_->addWidget(event);
|
||||
// } else {
|
||||
// Update the current widget with the new data.
|
||||
// auto userWidget = qobject_cast<UserItem *>(item->widget());
|
||||
// if (userWidget)
|
||||
// userWidget->updateItem(users.at(i).user_id);
|
||||
// }
|
||||
userItem_->updateItem(user);
|
||||
|
||||
msgLabel_->setText(msg);
|
||||
|
||||
eventLabel_->setText(srcEvent);
|
||||
|
||||
adjustSize();
|
||||
}
|
||||
|
@ -58,3 +86,13 @@ ReplyPopup::paintEvent(QPaintEvent *)
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPoint>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include "../AvatarProvider.h"
|
||||
#include "../Cache.h"
|
||||
#include "../ChatPage.h"
|
||||
#include "../ui/FlatButton.h"
|
||||
#include "PopupItem.h"
|
||||
|
||||
class ReplyPopup : public QWidget
|
||||
|
@ -22,10 +24,22 @@ public slots:
|
|||
|
||||
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:
|
||||
QVBoxLayout *layout_;
|
||||
QHBoxLayout *topLayout_;
|
||||
QVBoxLayout *mainLayout_;
|
||||
QHBoxLayout *buttonLayout_;
|
||||
|
||||
UserItem *userItem_;
|
||||
FlatButton *closeBtn_;
|
||||
QLabel *msgLabel_;
|
||||
QLabel *eventLabel_;
|
||||
|
||||
int buttonSize_;
|
||||
};
|
||||
|
|
|
@ -874,8 +874,12 @@ TimelineItem::replyAction()
|
|||
if (!body_)
|
||||
return;
|
||||
|
||||
emit ChatPage::instance()->messageReply(
|
||||
Cache::displayName(room_id_, descriptionMsg_.userid), body_->toPlainText(), eventId());
|
||||
RelatedInfo related;
|
||||
related.quoted_body = body_->toPlainText();
|
||||
related.quoted_user = descriptionMsg_.userid;
|
||||
related.related_event = eventId().toStdString();
|
||||
|
||||
emit ChatPage::instance()->messageReply(related);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -692,7 +692,7 @@ TimelineView::updatePendingMessage(const std::string &txn_id, const QString &eve
|
|||
void
|
||||
TimelineView::addUserMessage(mtx::events::MessageType ty,
|
||||
const QString &body,
|
||||
const QString &related_event)
|
||||
const RelatedInfo &related = RelatedInfo())
|
||||
{
|
||||
auto with_sender = (lastSender_ != local_user_) || isDateDifference(lastMsgTimestamp_);
|
||||
|
||||
|
@ -700,13 +700,11 @@ TimelineView::addUserMessage(mtx::events::MessageType ty,
|
|||
new TimelineItem(ty, local_user_, body, with_sender, room_id_, scroll_widget_);
|
||||
|
||||
PendingMessage message;
|
||||
message.ty = ty;
|
||||
message.txn_id = http::client()->generate_txn_id();
|
||||
message.body = body;
|
||||
message.widget = view_item;
|
||||
if (!related_event.isEmpty()) {
|
||||
message.related_event = related_event.toStdString();
|
||||
}
|
||||
message.ty = ty;
|
||||
message.txn_id = http::client()->generate_txn_id();
|
||||
message.body = body;
|
||||
message.widget = view_item;
|
||||
message.related = related;
|
||||
|
||||
try {
|
||||
message.is_encrypted = cache::client()->isRoomEncrypted(room_id_.toStdString());
|
||||
|
@ -730,7 +728,7 @@ TimelineView::addUserMessage(mtx::events::MessageType ty,
|
|||
void
|
||||
TimelineView::addUserMessage(mtx::events::MessageType ty, const QString &body)
|
||||
{
|
||||
addUserMessage(ty, body, "");
|
||||
addUserMessage(ty, body, RelatedInfo());
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1273,13 +1271,20 @@ toRoomMessage<mtx::events::msg::Text>(const PendingMessage &m)
|
|||
auto html = utils::markdownToHtml(m.body);
|
||||
|
||||
mtx::events::msg::Text text;
|
||||
|
||||
text.body = m.body.trimmed().toStdString();
|
||||
|
||||
if (html != m.body.trimmed().toHtmlEscaped())
|
||||
text.formatted_body = html.toStdString();
|
||||
if (html != m.body.trimmed().toHtmlEscaped()) {
|
||||
if (!m.related.quoted_body.isEmpty()) {
|
||||
text.formatted_body =
|
||||
utils::getFormattedQuoteBody(m.related, html).toStdString();
|
||||
} else {
|
||||
text.formatted_body = html.toStdString();
|
||||
}
|
||||
}
|
||||
|
||||
if (!m.related_event.empty()) {
|
||||
text.relates_to.in_reply_to.event_id = m.related_event;
|
||||
if (!m.related.related_event.empty()) {
|
||||
text.relates_to.in_reply_to.event_id = m.related.related_event;
|
||||
}
|
||||
|
||||
return text;
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <mtx/events.hpp>
|
||||
#include <mtx/responses/messages.hpp>
|
||||
|
||||
#include "../Utils.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "timeline/TimelineItem.h"
|
||||
|
||||
|
@ -63,7 +64,7 @@ struct PendingMessage
|
|||
{
|
||||
mtx::events::MessageType ty;
|
||||
std::string txn_id;
|
||||
std::string related_event;
|
||||
RelatedInfo related;
|
||||
QString body;
|
||||
QString filename;
|
||||
QString mime;
|
||||
|
@ -123,7 +124,7 @@ public:
|
|||
void addEvents(const mtx::responses::Timeline &timeline);
|
||||
void addUserMessage(mtx::events::MessageType ty,
|
||||
const QString &body,
|
||||
const QString &related_event);
|
||||
const RelatedInfo &related);
|
||||
void addUserMessage(mtx::events::MessageType ty, const QString &msg);
|
||||
|
||||
template<class Widget, mtx::events::MessageType MsgType>
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
#include "Cache.h"
|
||||
#include "Logging.h"
|
||||
#include "Utils.h"
|
||||
#include "timeline/TimelineView.h"
|
||||
#include "timeline/TimelineViewManager.h"
|
||||
#include "timeline/widgets/AudioItem.h"
|
||||
|
@ -79,7 +80,7 @@ TimelineViewManager::queueEmoteMessage(const QString &msg)
|
|||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::queueReplyMessage(const QString &reply, const QString &related_event)
|
||||
TimelineViewManager::queueReplyMessage(const QString &reply, const RelatedInfo &related)
|
||||
{
|
||||
if (active_room_.isEmpty())
|
||||
return;
|
||||
|
@ -87,7 +88,7 @@ TimelineViewManager::queueReplyMessage(const QString &reply, const QString &rela
|
|||
auto room_id = active_room_;
|
||||
auto view = views_[room_id];
|
||||
|
||||
view->addUserMessage(mtx::events::MessageType::Text, reply, related_event);
|
||||
view->addUserMessage(mtx::events::MessageType::Text, reply, related);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
|
||||
#include <mtx.hpp>
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
class QFile;
|
||||
|
||||
class RoomInfoListItem;
|
||||
|
@ -63,7 +65,7 @@ public slots:
|
|||
|
||||
void setHistoryView(const QString &room_id);
|
||||
void queueTextMessage(const QString &msg);
|
||||
void queueReplyMessage(const QString &reply, const QString &related_event);
|
||||
void queueReplyMessage(const QString &reply, const RelatedInfo &related);
|
||||
void queueEmoteMessage(const QString &msg);
|
||||
void queueImageMessage(const QString &roomid,
|
||||
const QString &filename,
|
||||
|
|
Loading…
Reference in a new issue