mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-26 04:58:49 +03:00
Add ability to redact messages
This commit is contained in:
parent
a6f867353f
commit
a0ae6cf5d5
9 changed files with 216 additions and 79 deletions
|
@ -72,6 +72,10 @@ public:
|
||||||
{
|
{
|
||||||
client_->readEvent(room_id, event_id);
|
client_->readEvent(room_id, event_id);
|
||||||
}
|
}
|
||||||
|
void redactEvent(const QString &room_id, const QString &event_id)
|
||||||
|
{
|
||||||
|
client_->redactEvent(room_id, event_id);
|
||||||
|
}
|
||||||
|
|
||||||
QSharedPointer<UserSettings> userSettings() { return userSettings_; }
|
QSharedPointer<UserSettings> userSettings() { return userSettings_; }
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,7 @@ public:
|
||||||
void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000);
|
void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000);
|
||||||
void removeTypingNotification(const QString &roomid);
|
void removeTypingNotification(const QString &roomid);
|
||||||
void readEvent(const QString &room_id, const QString &event_id);
|
void readEvent(const QString &room_id, const QString &event_id);
|
||||||
|
void redactEvent(const QString &room_id, const QString &event_id);
|
||||||
void inviteUser(const QString &room_id, const QString &user);
|
void inviteUser(const QString &room_id, const QString &user);
|
||||||
void createRoom(const mtx::requests::CreateRoom &request);
|
void createRoom(const mtx::requests::CreateRoom &request);
|
||||||
|
|
||||||
|
@ -171,6 +172,9 @@ signals:
|
||||||
void leftRoom(const QString &room_id);
|
void leftRoom(const QString &room_id);
|
||||||
void roomCreationFailed(const QString &msg);
|
void roomCreationFailed(const QString &msg);
|
||||||
|
|
||||||
|
void redactionFailed(const QString &error);
|
||||||
|
void redactionCompleted(const QString &room_id, const QString &event_id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QNetworkReply *makeUploadRequest(QSharedPointer<QIODevice> iodev);
|
QNetworkReply *makeUploadRequest(QSharedPointer<QIODevice> iodev);
|
||||||
QJsonObject getUploadReply(QNetworkReply *reply);
|
QJsonObject getUploadReply(QNetworkReply *reply);
|
||||||
|
|
|
@ -93,6 +93,9 @@ public:
|
||||||
ChatPage::instance()->readEvent(room_id_, event_id_);
|
ChatPage::instance()->readEvent(room_id_, event_id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Add a user avatar for this event.
|
||||||
|
void addAvatar();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *event) override;
|
void paintEvent(QPaintEvent *event) override;
|
||||||
void contextMenuEvent(QContextMenuEvent *event) override;
|
void contextMenuEvent(QContextMenuEvent *event) override;
|
||||||
|
@ -130,20 +133,18 @@ private:
|
||||||
QMenu *contextMenu_;
|
QMenu *contextMenu_;
|
||||||
QAction *showReadReceipts_;
|
QAction *showReadReceipts_;
|
||||||
QAction *markAsRead_;
|
QAction *markAsRead_;
|
||||||
|
QAction *redactMsg_;
|
||||||
|
|
||||||
QHBoxLayout *topLayout_;
|
QHBoxLayout *topLayout_ = nullptr;
|
||||||
//! The message and the timestamp/checkmark.
|
QHBoxLayout *messageLayout_ = nullptr;
|
||||||
QHBoxLayout *messageLayout_;
|
QVBoxLayout *mainLayout_ = nullptr;
|
||||||
//! Avatar or Timestamp
|
QVBoxLayout *headerLayout_ = nullptr;
|
||||||
QVBoxLayout *sideLayout_;
|
QHBoxLayout *widgetLayout_ = nullptr;
|
||||||
//! Header & Message body
|
|
||||||
QVBoxLayout *mainLayout_;
|
|
||||||
|
|
||||||
QVBoxLayout *headerLayout_; // Username (&) Timestamp
|
|
||||||
|
|
||||||
Avatar *userAvatar_;
|
Avatar *userAvatar_;
|
||||||
|
|
||||||
QFont font_;
|
QFont font_;
|
||||||
|
QFont usernameFont_;
|
||||||
|
|
||||||
QLabel *timestamp_;
|
QLabel *timestamp_;
|
||||||
QLabel *checkmark_;
|
QLabel *checkmark_;
|
||||||
|
@ -169,26 +170,23 @@ TimelineItem::setupLocalWidgetLayout(Widget *widget,
|
||||||
|
|
||||||
generateTimestamp(timestamp);
|
generateTimestamp(timestamp);
|
||||||
|
|
||||||
auto widgetLayout = new QHBoxLayout();
|
widgetLayout_ = new QHBoxLayout;
|
||||||
widgetLayout->setContentsMargins(0, 5, 0, 0);
|
widgetLayout_->setContentsMargins(0, 5, 0, 0);
|
||||||
widgetLayout->addWidget(widget);
|
widgetLayout_->addWidget(widget);
|
||||||
widgetLayout->addStretch(1);
|
widgetLayout_->addStretch(1);
|
||||||
|
|
||||||
messageLayout_->setContentsMargins(0, 0, 20, 4);
|
|
||||||
messageLayout_->setSpacing(20);
|
|
||||||
|
|
||||||
if (withSender) {
|
if (withSender) {
|
||||||
generateBody(displayName, "");
|
generateBody(displayName, "");
|
||||||
setupAvatarLayout(displayName);
|
setupAvatarLayout(displayName);
|
||||||
|
|
||||||
headerLayout_->addLayout(widgetLayout);
|
headerLayout_->addLayout(widgetLayout_);
|
||||||
messageLayout_->addLayout(headerLayout_, 1);
|
messageLayout_->addLayout(headerLayout_, 1);
|
||||||
|
|
||||||
AvatarProvider::resolve(userid, [this](const QImage &img) { setUserAvatar(img); });
|
AvatarProvider::resolve(userid, [this](const QImage &img) { setUserAvatar(img); });
|
||||||
} else {
|
} else {
|
||||||
setupSimpleLayout();
|
setupSimpleLayout();
|
||||||
|
|
||||||
messageLayout_->addLayout(widgetLayout, 1);
|
messageLayout_->addLayout(widgetLayout_, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
messageLayout_->addWidget(checkmark_);
|
messageLayout_->addWidget(checkmark_);
|
||||||
|
@ -220,26 +218,23 @@ TimelineItem::setupWidgetLayout(Widget *widget,
|
||||||
|
|
||||||
generateTimestamp(timestamp);
|
generateTimestamp(timestamp);
|
||||||
|
|
||||||
auto widgetLayout = new QHBoxLayout();
|
widgetLayout_ = new QHBoxLayout();
|
||||||
widgetLayout->setContentsMargins(0, 5, 0, 0);
|
widgetLayout_->setContentsMargins(0, 5, 0, 0);
|
||||||
widgetLayout->addWidget(widget);
|
widgetLayout_->addWidget(widget);
|
||||||
widgetLayout->addStretch(1);
|
widgetLayout_->addStretch(1);
|
||||||
|
|
||||||
messageLayout_->setContentsMargins(0, 0, 20, 4);
|
|
||||||
messageLayout_->setSpacing(20);
|
|
||||||
|
|
||||||
if (withSender) {
|
if (withSender) {
|
||||||
generateBody(displayName, "");
|
generateBody(displayName, "");
|
||||||
setupAvatarLayout(displayName);
|
setupAvatarLayout(displayName);
|
||||||
|
|
||||||
headerLayout_->addLayout(widgetLayout);
|
headerLayout_->addLayout(widgetLayout_);
|
||||||
messageLayout_->addLayout(headerLayout_, 1);
|
messageLayout_->addLayout(headerLayout_, 1);
|
||||||
|
|
||||||
AvatarProvider::resolve(sender, [this](const QImage &img) { setUserAvatar(img); });
|
AvatarProvider::resolve(sender, [this](const QImage &img) { setUserAvatar(img); });
|
||||||
} else {
|
} else {
|
||||||
setupSimpleLayout();
|
setupSimpleLayout();
|
||||||
|
|
||||||
messageLayout_->addLayout(widgetLayout, 1);
|
messageLayout_->addLayout(widgetLayout_, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
messageLayout_->addWidget(checkmark_);
|
messageLayout_->addWidget(checkmark_);
|
||||||
|
|
|
@ -101,6 +101,9 @@ public:
|
||||||
void scrollDown();
|
void scrollDown();
|
||||||
QLabel *createDateSeparator(QDateTime datetime);
|
QLabel *createDateSeparator(QDateTime datetime);
|
||||||
|
|
||||||
|
//! Remove an item from the timeline with the given Event ID.
|
||||||
|
void removeEvent(const QString &event_id);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void sliderRangeChanged(int min, int max);
|
void sliderRangeChanged(int min, int max);
|
||||||
void sliderMoved(int position);
|
void sliderMoved(int position);
|
||||||
|
@ -128,6 +131,8 @@ protected:
|
||||||
private:
|
private:
|
||||||
using TimelineEvent = mtx::events::collections::TimelineEvents;
|
using TimelineEvent = mtx::events::collections::TimelineEvents;
|
||||||
|
|
||||||
|
QWidget *relativeWidget(TimelineItem *item, int dt) const;
|
||||||
|
|
||||||
//! HACK: Fixing layout flickering when adding to the bottom
|
//! HACK: Fixing layout flickering when adding to the bottom
|
||||||
//! of the timeline.
|
//! of the timeline.
|
||||||
void pushTimelineItem(TimelineItem *item)
|
void pushTimelineItem(TimelineItem *item)
|
||||||
|
@ -232,7 +237,7 @@ private:
|
||||||
inline bool isNotifiable(const TimelineEvent &event) const;
|
inline bool isNotifiable(const TimelineEvent &event) const;
|
||||||
|
|
||||||
// The events currently rendered. Used for duplicate detection.
|
// The events currently rendered. Used for duplicate detection.
|
||||||
QMap<QString, bool> eventIds_;
|
QMap<QString, TimelineItem *> eventIds_;
|
||||||
QQueue<PendingMessage> pending_msgs_;
|
QQueue<PendingMessage> pending_msgs_;
|
||||||
QList<PendingMessage> pending_sent_msgs_;
|
QList<PendingMessage> pending_sent_msgs_;
|
||||||
QSharedPointer<MatrixClient> client_;
|
QSharedPointer<MatrixClient> client_;
|
||||||
|
@ -295,13 +300,9 @@ TimelineView::processMessageEvent(const Event &event, TimelineDirection directio
|
||||||
const auto event_id = QString::fromStdString(event.event_id);
|
const auto event_id = QString::fromStdString(event.event_id);
|
||||||
const auto sender = QString::fromStdString(event.sender);
|
const auto sender = QString::fromStdString(event.sender);
|
||||||
|
|
||||||
if (isDuplicate(event_id))
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
eventIds_[event_id] = true;
|
|
||||||
|
|
||||||
const QString txnid = QString::fromStdString(event.unsigned_data.transaction_id);
|
const QString txnid = QString::fromStdString(event.unsigned_data.transaction_id);
|
||||||
if (!txnid.isEmpty() && isPendingMessage(txnid, sender, local_user_)) {
|
if ((!txnid.isEmpty() && isPendingMessage(txnid, sender, local_user_)) ||
|
||||||
|
isDuplicate(event_id)) {
|
||||||
removePendingMessage(txnid);
|
removePendingMessage(txnid);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -310,7 +311,11 @@ TimelineView::processMessageEvent(const Event &event, TimelineDirection directio
|
||||||
|
|
||||||
updateLastSender(sender, direction);
|
updateLastSender(sender, direction);
|
||||||
|
|
||||||
return createTimelineItem<Event>(event, with_sender);
|
auto item = createTimelineItem<Event>(event, with_sender);
|
||||||
|
|
||||||
|
eventIds_[event_id] = item;
|
||||||
|
|
||||||
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Event, class Widget>
|
template<class Event, class Widget>
|
||||||
|
@ -320,13 +325,9 @@ TimelineView::processMessageEvent(const Event &event, TimelineDirection directio
|
||||||
const auto event_id = QString::fromStdString(event.event_id);
|
const auto event_id = QString::fromStdString(event.event_id);
|
||||||
const auto sender = QString::fromStdString(event.sender);
|
const auto sender = QString::fromStdString(event.sender);
|
||||||
|
|
||||||
if (isDuplicate(event_id))
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
eventIds_[event_id] = true;
|
|
||||||
|
|
||||||
const QString txnid = QString::fromStdString(event.unsigned_data.transaction_id);
|
const QString txnid = QString::fromStdString(event.unsigned_data.transaction_id);
|
||||||
if (!txnid.isEmpty() && isPendingMessage(txnid, sender, local_user_)) {
|
if ((!txnid.isEmpty() && isPendingMessage(txnid, sender, local_user_)) ||
|
||||||
|
isDuplicate(event_id)) {
|
||||||
removePendingMessage(txnid);
|
removePendingMessage(txnid);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -335,5 +336,9 @@ TimelineView::processMessageEvent(const Event &event, TimelineDirection directio
|
||||||
|
|
||||||
updateLastSender(sender, direction);
|
updateLastSender(sender, direction);
|
||||||
|
|
||||||
return createTimelineItem<Event, Widget>(event, with_sender);
|
auto item = createTimelineItem<Event, Widget>(event, with_sender);
|
||||||
|
|
||||||
|
eventIds_[event_id] = item;
|
||||||
|
|
||||||
|
return item;
|
||||||
}
|
}
|
||||||
|
|
|
@ -342,6 +342,9 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client,
|
||||||
emit showNotification(QString("Room %1 created").arg(room_id));
|
emit showNotification(QString("Room %1 created").arg(room_id));
|
||||||
});
|
});
|
||||||
connect(client_.data(), &MatrixClient::leftRoom, this, &ChatPage::removeRoom);
|
connect(client_.data(), &MatrixClient::leftRoom, this, &ChatPage::removeRoom);
|
||||||
|
connect(client_.data(), &MatrixClient::redactionFailed, this, [this](const QString &error) {
|
||||||
|
emit showNotification(QString("Message redaction failed: %1").arg(error));
|
||||||
|
});
|
||||||
|
|
||||||
showContentTimer_ = new QTimer(this);
|
showContentTimer_ = new QTimer(this);
|
||||||
showContentTimer_->setSingleShot(true);
|
showContentTimer_->setSingleShot(true);
|
||||||
|
|
|
@ -1281,3 +1281,47 @@ MatrixClient::getUploadReply(QNetworkReply *reply)
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MatrixClient::redactEvent(const QString &room_id, const QString &event_id)
|
||||||
|
{
|
||||||
|
QUrlQuery query;
|
||||||
|
query.addQueryItem("access_token", token_);
|
||||||
|
|
||||||
|
QUrl endpoint(server_);
|
||||||
|
endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/redact/%2/%3")
|
||||||
|
.arg(room_id)
|
||||||
|
.arg(event_id)
|
||||||
|
.arg(incrementTransactionId()));
|
||||||
|
endpoint.setQuery(query);
|
||||||
|
|
||||||
|
QNetworkRequest request(QString(endpoint.toEncoded()));
|
||||||
|
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
// TODO: no reason specified
|
||||||
|
QJsonObject body{};
|
||||||
|
auto reply = put(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [reply, this, room_id, event_id]() {
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
auto data = reply->readAll();
|
||||||
|
|
||||||
|
if (status == 0 || status >= 400) {
|
||||||
|
try {
|
||||||
|
mtx::errors::Error res = nlohmann::json::parse(data);
|
||||||
|
emit redactionFailed(QString::fromStdString(res.error));
|
||||||
|
return;
|
||||||
|
} catch (const std::exception &) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mtx::responses::EventId res = nlohmann::json::parse(data);
|
||||||
|
emit redactionCompleted(room_id, event_id);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
emit redactionFailed(QString::fromStdString(e.what()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -40,29 +40,39 @@ TimelineItem::init()
|
||||||
body_ = nullptr;
|
body_ = nullptr;
|
||||||
|
|
||||||
font_.setPixelSize(conf::fontSize);
|
font_.setPixelSize(conf::fontSize);
|
||||||
|
usernameFont_ = font_;
|
||||||
|
usernameFont_.setWeight(60);
|
||||||
|
|
||||||
QFontMetrics fm(font_);
|
QFontMetrics fm(font_);
|
||||||
|
|
||||||
contextMenu_ = new QMenu(this);
|
contextMenu_ = new QMenu(this);
|
||||||
showReadReceipts_ = new QAction("Read receipts", this);
|
showReadReceipts_ = new QAction("Read receipts", this);
|
||||||
markAsRead_ = new QAction("Mark as read", this);
|
markAsRead_ = new QAction("Mark as read", this);
|
||||||
|
redactMsg_ = new QAction("Redact message", this);
|
||||||
contextMenu_->addAction(showReadReceipts_);
|
contextMenu_->addAction(showReadReceipts_);
|
||||||
contextMenu_->addAction(markAsRead_);
|
contextMenu_->addAction(markAsRead_);
|
||||||
|
contextMenu_->addAction(redactMsg_);
|
||||||
|
|
||||||
connect(showReadReceipts_, &QAction::triggered, this, [this]() {
|
connect(showReadReceipts_, &QAction::triggered, this, [this]() {
|
||||||
if (!event_id_.isEmpty())
|
if (!event_id_.isEmpty())
|
||||||
ChatPage::instance()->showReadReceipts(event_id_);
|
ChatPage::instance()->showReadReceipts(event_id_);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(redactMsg_, &QAction::triggered, this, [this]() {
|
||||||
|
if (!event_id_.isEmpty())
|
||||||
|
ChatPage::instance()->redactEvent(room_id_, event_id_);
|
||||||
|
});
|
||||||
|
|
||||||
connect(markAsRead_, &QAction::triggered, this, [this]() { sendReadReceipt(); });
|
connect(markAsRead_, &QAction::triggered, this, [this]() { sendReadReceipt(); });
|
||||||
|
|
||||||
topLayout_ = new QHBoxLayout(this);
|
topLayout_ = new QHBoxLayout(this);
|
||||||
mainLayout_ = new QVBoxLayout;
|
mainLayout_ = new QVBoxLayout;
|
||||||
messageLayout_ = new QHBoxLayout;
|
messageLayout_ = new QHBoxLayout;
|
||||||
|
messageLayout_->setContentsMargins(0, 0, 20, 4);
|
||||||
|
messageLayout_->setSpacing(20);
|
||||||
|
|
||||||
topLayout_->setContentsMargins(conf::timeline::msgMargin, conf::timeline::msgMargin, 0, 0);
|
topLayout_->setContentsMargins(conf::timeline::msgMargin, conf::timeline::msgMargin, 0, 0);
|
||||||
topLayout_->setSpacing(0);
|
topLayout_->setSpacing(0);
|
||||||
|
|
||||||
topLayout_->addLayout(mainLayout_, 1);
|
topLayout_->addLayout(mainLayout_, 1);
|
||||||
|
|
||||||
mainLayout_->setContentsMargins(conf::timeline::headerLeftMargin, 0, 0, 0);
|
mainLayout_->setContentsMargins(conf::timeline::headerLeftMargin, 0, 0, 0);
|
||||||
|
@ -73,7 +83,7 @@ TimelineItem::init()
|
||||||
|
|
||||||
// Setting fixed width for checkmark because systems may have a differing width for a
|
// Setting fixed width for checkmark because systems may have a differing width for a
|
||||||
// space and the Unicode checkmark.
|
// space and the Unicode checkmark.
|
||||||
checkmark_ = new QLabel(" ", this);
|
checkmark_ = new QLabel(this);
|
||||||
checkmark_->setFont(checkmarkFont);
|
checkmark_->setFont(checkmarkFont);
|
||||||
checkmark_->setFixedWidth(QFontMetrics{checkmarkFont}.width(CHECKMARK));
|
checkmark_->setFixedWidth(QFontMetrics{checkmarkFont}.width(CHECKMARK));
|
||||||
}
|
}
|
||||||
|
@ -106,9 +116,6 @@ TimelineItem::TimelineItem(mtx::events::MessageType ty,
|
||||||
body.replace("\n", "<br/>");
|
body.replace("\n", "<br/>");
|
||||||
generateTimestamp(timestamp);
|
generateTimestamp(timestamp);
|
||||||
|
|
||||||
messageLayout_->setContentsMargins(0, 0, 20, 4);
|
|
||||||
messageLayout_->setSpacing(20);
|
|
||||||
|
|
||||||
if (withSender) {
|
if (withSender) {
|
||||||
generateBody(displayName, body);
|
generateBody(displayName, body);
|
||||||
setupAvatarLayout(displayName);
|
setupAvatarLayout(displayName);
|
||||||
|
@ -240,9 +247,6 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice
|
||||||
body.replace("\n", "<br/>");
|
body.replace("\n", "<br/>");
|
||||||
body = "<i>" + body + "</i>";
|
body = "<i>" + body + "</i>";
|
||||||
|
|
||||||
messageLayout_->setContentsMargins(0, 0, 20, 4);
|
|
||||||
messageLayout_->setSpacing(20);
|
|
||||||
|
|
||||||
if (with_sender) {
|
if (with_sender) {
|
||||||
auto displayName = TimelineViewManager::displayName(sender);
|
auto displayName = TimelineViewManager::displayName(sender);
|
||||||
|
|
||||||
|
@ -289,9 +293,6 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote>
|
||||||
emoteMsg.replace(conf::strings::url_regex, conf::strings::url_html);
|
emoteMsg.replace(conf::strings::url_regex, conf::strings::url_html);
|
||||||
emoteMsg.replace("\n", "<br/>");
|
emoteMsg.replace("\n", "<br/>");
|
||||||
|
|
||||||
messageLayout_->setContentsMargins(0, 0, 20, 4);
|
|
||||||
messageLayout_->setSpacing(20);
|
|
||||||
|
|
||||||
if (with_sender) {
|
if (with_sender) {
|
||||||
generateBody(displayName, emoteMsg);
|
generateBody(displayName, emoteMsg);
|
||||||
setupAvatarLayout(displayName);
|
setupAvatarLayout(displayName);
|
||||||
|
@ -341,9 +342,6 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text>
|
||||||
body.replace(conf::strings::url_regex, conf::strings::url_html);
|
body.replace(conf::strings::url_regex, conf::strings::url_html);
|
||||||
body.replace("\n", "<br/>");
|
body.replace("\n", "<br/>");
|
||||||
|
|
||||||
messageLayout_->setContentsMargins(0, 0, 20, 4);
|
|
||||||
messageLayout_->setSpacing(20);
|
|
||||||
|
|
||||||
if (with_sender) {
|
if (with_sender) {
|
||||||
generateBody(displayName, body);
|
generateBody(displayName, body);
|
||||||
setupAvatarLayout(displayName);
|
setupAvatarLayout(displayName);
|
||||||
|
@ -400,25 +398,13 @@ TimelineItem::generateBody(const QString &userid, const QString &body)
|
||||||
sender = userid.split(":")[0].split("@")[1];
|
sender = userid.split(":")[0].split("@")[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
QFont usernameFont = font_;
|
QFontMetrics fm(usernameFont_);
|
||||||
usernameFont.setWeight(60);
|
|
||||||
|
|
||||||
QFontMetrics fm(usernameFont);
|
|
||||||
|
|
||||||
userName_ = new QLabel(this);
|
userName_ = new QLabel(this);
|
||||||
userName_->setFont(usernameFont);
|
userName_->setFont(usernameFont_);
|
||||||
userName_->setText(fm.elidedText(sender, Qt::ElideRight, 500));
|
userName_->setText(fm.elidedText(sender, Qt::ElideRight, 500));
|
||||||
|
|
||||||
if (body.isEmpty())
|
generateBody(body);
|
||||||
return;
|
|
||||||
|
|
||||||
body_ = new QLabel(this);
|
|
||||||
body_->setFont(font_);
|
|
||||||
body_->setWordWrap(true);
|
|
||||||
body_->setText(QString("<span>%1</span>").arg(replaceEmoji(body)));
|
|
||||||
body_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
|
|
||||||
body_->setOpenExternalLinks(true);
|
|
||||||
body_->setMargin(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -474,12 +460,8 @@ TimelineItem::setupAvatarLayout(const QString &userName)
|
||||||
if (userName[0] == '@' && userName.size() > 1)
|
if (userName[0] == '@' && userName.size() > 1)
|
||||||
userAvatar_->setLetter(QChar(userName[1]).toUpper());
|
userAvatar_->setLetter(QChar(userName[1]).toUpper());
|
||||||
|
|
||||||
sideLayout_ = new QVBoxLayout;
|
topLayout_->insertWidget(0, userAvatar_);
|
||||||
sideLayout_->setMargin(0);
|
topLayout_->setAlignment(userAvatar_, Qt::AlignTop);
|
||||||
sideLayout_->setSpacing(0);
|
|
||||||
sideLayout_->addWidget(userAvatar_);
|
|
||||||
sideLayout_->addStretch(1);
|
|
||||||
topLayout_->insertLayout(0, sideLayout_);
|
|
||||||
|
|
||||||
headerLayout_ = new QVBoxLayout;
|
headerLayout_ = new QVBoxLayout;
|
||||||
headerLayout_->setMargin(0);
|
headerLayout_->setMargin(0);
|
||||||
|
@ -492,8 +474,8 @@ TimelineItem::setupAvatarLayout(const QString &userName)
|
||||||
void
|
void
|
||||||
TimelineItem::setupSimpleLayout()
|
TimelineItem::setupSimpleLayout()
|
||||||
{
|
{
|
||||||
topLayout_->setContentsMargins(conf::timeline::avatarSize + conf::timeline::msgMargin + 1,
|
topLayout_->setContentsMargins(conf::timeline::msgMargin + conf::timeline::avatarSize + 2,
|
||||||
conf::timeline::msgMargin / 3,
|
conf::timeline::msgMargin,
|
||||||
0,
|
0,
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
@ -533,3 +515,48 @@ TimelineItem::addSaveImageAction(ImageItem *image)
|
||||||
connect(saveImage, &QAction::triggered, image, &ImageItem::saveAs);
|
connect(saveImage, &QAction::triggered, image, &ImageItem::saveAs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineItem::addAvatar()
|
||||||
|
{
|
||||||
|
if (userAvatar_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// TODO: should be replaced with the proper event struct.
|
||||||
|
auto userid = descriptionMsg_.userid;
|
||||||
|
auto displayName = TimelineViewManager::displayName(userid);
|
||||||
|
|
||||||
|
QFontMetrics fm(usernameFont_);
|
||||||
|
userName_ = new QLabel(this);
|
||||||
|
userName_->setFont(usernameFont_);
|
||||||
|
userName_->setText(fm.elidedText(displayName, Qt::ElideRight, 500));
|
||||||
|
|
||||||
|
QWidget *widget = nullptr;
|
||||||
|
|
||||||
|
// Extract the widget before we delete its layout.
|
||||||
|
if (widgetLayout_)
|
||||||
|
widget = widgetLayout_->itemAt(0)->widget();
|
||||||
|
|
||||||
|
// Remove all items from the layout.
|
||||||
|
QLayoutItem *item;
|
||||||
|
while ((item = messageLayout_->takeAt(0)) != 0)
|
||||||
|
delete item;
|
||||||
|
|
||||||
|
setupAvatarLayout(displayName);
|
||||||
|
|
||||||
|
// Restore widget's layout.
|
||||||
|
if (widget) {
|
||||||
|
widgetLayout_ = new QHBoxLayout();
|
||||||
|
widgetLayout_->setContentsMargins(0, 5, 0, 0);
|
||||||
|
widgetLayout_->addWidget(widget);
|
||||||
|
widgetLayout_->addStretch(1);
|
||||||
|
|
||||||
|
headerLayout_->addLayout(widgetLayout_);
|
||||||
|
}
|
||||||
|
|
||||||
|
messageLayout_->addLayout(headerLayout_, 1);
|
||||||
|
messageLayout_->addWidget(checkmark_);
|
||||||
|
messageLayout_->addWidget(timestamp_);
|
||||||
|
|
||||||
|
AvatarProvider::resolve(userid, [this](const QImage &img) { setUserAvatar(img); });
|
||||||
|
}
|
||||||
|
|
|
@ -491,6 +491,7 @@ TimelineView::updatePendingMessage(int txn_id, QString event_id)
|
||||||
if (msg.widget) {
|
if (msg.widget) {
|
||||||
msg.widget->setEventId(event_id);
|
msg.widget->setEventId(event_id);
|
||||||
msg.widget->markReceived();
|
msg.widget->markReceived();
|
||||||
|
eventIds_[event_id] = msg.widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
pending_sent_msgs_.append(msg);
|
pending_sent_msgs_.append(msg);
|
||||||
|
@ -591,6 +592,9 @@ TimelineView::isPendingMessage(const QString &txnid,
|
||||||
void
|
void
|
||||||
TimelineView::removePendingMessage(const QString &txnid)
|
TimelineView::removePendingMessage(const QString &txnid)
|
||||||
{
|
{
|
||||||
|
if (txnid.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
for (auto it = pending_sent_msgs_.begin(); it != pending_sent_msgs_.end(); ++it) {
|
for (auto it = pending_sent_msgs_.begin(); it != pending_sent_msgs_.end(); ++it) {
|
||||||
if (QString::number(it->txn_id) == txnid) {
|
if (QString::number(it->txn_id) == txnid) {
|
||||||
int index = std::distance(pending_sent_msgs_.begin(), it);
|
int index = std::distance(pending_sent_msgs_.begin(), it);
|
||||||
|
@ -739,3 +743,44 @@ TimelineView::toggleScrollDownButton()
|
||||||
scrollDownBtn_->hide();
|
scrollDownBtn_->hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineView::removeEvent(const QString &event_id)
|
||||||
|
{
|
||||||
|
if (!eventIds_.contains(event_id)) {
|
||||||
|
qWarning() << "unknown event_id couldn't be removed:" << event_id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto removedItem = eventIds_[event_id];
|
||||||
|
|
||||||
|
// Find the next and the previous widgets in the timeline
|
||||||
|
auto prevItem = qobject_cast<TimelineItem *>(relativeWidget(removedItem, -1));
|
||||||
|
auto nextItem = qobject_cast<TimelineItem *>(relativeWidget(removedItem, 1));
|
||||||
|
|
||||||
|
// If it's a TimelineItem add an avatar.
|
||||||
|
if (prevItem)
|
||||||
|
prevItem->addAvatar();
|
||||||
|
|
||||||
|
if (nextItem)
|
||||||
|
nextItem->addAvatar();
|
||||||
|
|
||||||
|
// Finally remove the event.
|
||||||
|
removedItem->deleteLater();
|
||||||
|
eventIds_.remove(event_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget *
|
||||||
|
TimelineView::relativeWidget(TimelineItem *item, int dt) const
|
||||||
|
{
|
||||||
|
int pos = scroll_layout_->indexOf(item);
|
||||||
|
|
||||||
|
if (pos == -1)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
pos = pos + dt;
|
||||||
|
|
||||||
|
bool isOutOfBounds = (pos <= 0 || pos > scroll_layout_->count() - 1);
|
||||||
|
|
||||||
|
return isOutOfBounds ? nullptr : scroll_layout_->itemAt(pos)->widget();
|
||||||
|
}
|
||||||
|
|
|
@ -44,6 +44,16 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<MatrixClient> client, QW
|
||||||
&MatrixClient::messageSendFailed,
|
&MatrixClient::messageSendFailed,
|
||||||
this,
|
this,
|
||||||
&TimelineViewManager::messageSendFailed);
|
&TimelineViewManager::messageSendFailed);
|
||||||
|
|
||||||
|
connect(client_.data(),
|
||||||
|
&MatrixClient::redactionCompleted,
|
||||||
|
this,
|
||||||
|
[this](const QString &room_id, const QString &event_id) {
|
||||||
|
auto view = views_[room_id];
|
||||||
|
|
||||||
|
if (view)
|
||||||
|
view->removeEvent(event_id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
Loading…
Reference in a new issue