diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 5b838259..f7dbf7ca 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -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 &)),
diff --git a/src/ChatPage.h b/src/ChatPage.h
index f70f5bdd..6e6f5aed 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -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 &);
diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp
index dc41b4d3..8becf5ce 100644
--- a/src/TextInputWidget.cpp
+++ b/src/TextInputWidget.cpp
@@ -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);
}
diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h
index c462de05..f68560e9 100644
--- a/src/TextInputWidget.h
+++ b/src/TextInputWidget.h
@@ -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);
diff --git a/src/Utils.cpp b/src/Utils.cpp
index f8fdfaf9..690a9a9a 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -315,6 +315,20 @@ utils::markdownToHtml(const QString &text)
}
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()
{
QSettings settings;
diff --git a/src/Utils.h b/src/Utils.h
index 8672e7d4..bf941c4c 100644
--- a/src/Utils.h
+++ b/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();
diff --git a/src/popups/PopupItem.cpp b/src/popups/PopupItem.cpp
index 94de3a92..f905983a 100644
--- a/src/popups/PopupItem.cpp
+++ b/src/popups/PopupItem.cpp
@@ -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}
diff --git a/src/popups/PopupItem.h b/src/popups/PopupItem.h
index 1fc54bf7..cab73a9d 100644
--- a/src/popups/PopupItem.h
+++ b/src/popups/PopupItem.h
@@ -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);
diff --git a/src/popups/ReplyPopup.cpp b/src/popups/ReplyPopup.cpp
index 04c3cea5..3c7c482a 100644
--- a/src/popups/ReplyPopup.cpp
+++ b/src/popups/ReplyPopup.cpp
@@ -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);
+}
diff --git a/src/popups/ReplyPopup.h b/src/popups/ReplyPopup.h
index 57c2bc8a..829f707b 100644
--- a/src/popups/ReplyPopup.h
+++ b/src/popups/ReplyPopup.h
@@ -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_;
};
diff --git a/src/timeline/TimelineItem.cpp b/src/timeline/TimelineItem.cpp
index dd09ec78..bf5b1b53 100644
--- a/src/timeline/TimelineItem.cpp
+++ b/src/timeline/TimelineItem.cpp
@@ -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
diff --git a/src/timeline/TimelineView.cpp b/src/timeline/TimelineView.cpp
index 6d947c15..fc89fd38 100644
--- a/src/timeline/TimelineView.cpp
+++ b/src/timeline/TimelineView.cpp
@@ -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;
diff --git a/src/timeline/TimelineView.h b/src/timeline/TimelineView.h
index db6087eb..35796efd 100644
--- a/src/timeline/TimelineView.h
+++ b/src/timeline/TimelineView.h
@@ -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>
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 1ce3794f..86505481 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -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
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 0ac6d67e..b52136d9 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -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,
|