summary refs log tree commit diff
path: root/src/timeline/TimelineItem.h
diff options
context:
space:
mode:
authorKonstantinos Sideris <sideris.konstantin@gmail.com>2018-07-17 16:37:25 +0300
committerKonstantinos Sideris <sideris.konstantin@gmail.com>2018-07-17 16:37:25 +0300
commit0e814da91c8e041897a4c3f7e6e9234bbc7c6f7a (patch)
tree21f655d30630fe77ba48d07e4b357e2b6c6a5730 /src/timeline/TimelineItem.h
parentMerge pull request #372 from bebehei/notification (diff)
downloadnheko-0e814da91c8e041897a4c3f7e6e9234bbc7c6f7a.tar.xz
Move all files under src/
Diffstat (limited to 'src/timeline/TimelineItem.h')
-rw-r--r--src/timeline/TimelineItem.h380
1 files changed, 380 insertions, 0 deletions
diff --git a/src/timeline/TimelineItem.h b/src/timeline/TimelineItem.h
new file mode 100644

index 00000000..d3cab0a0 --- /dev/null +++ b/src/timeline/TimelineItem.h
@@ -0,0 +1,380 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <QAbstractTextDocumentLayout> +#include <QApplication> +#include <QDateTime> +#include <QHBoxLayout> +#include <QLabel> +#include <QLayout> +#include <QPainter> +#include <QSettings> +#include <QStyle> +#include <QStyleOption> +#include <QTextBrowser> +#include <QTimer> + +#include "AvatarProvider.h" +#include "RoomInfoListItem.h" +#include "Utils.h" + +#include "Cache.h" +#include "MatrixClient.h" + +class ImageItem; +class StickerItem; +class AudioItem; +class VideoItem; +class FileItem; +class Avatar; + +enum class StatusIndicatorState +{ + //! The encrypted message was received by the server. + Encrypted, + //! The plaintext message was received by the server. + Received, + //! The client sent the message. Not yet received. + Sent, + //! When the message is loaded from cache or backfill. + Empty, +}; + +//! +//! Used to notify the user about the status of a message. +//! +class StatusIndicator : public QWidget +{ + Q_OBJECT + +public: + explicit StatusIndicator(QWidget *parent); + void setState(StatusIndicatorState state); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + void paintIcon(QPainter &p, QIcon &icon); + + QIcon lockIcon_; + QIcon clockIcon_; + QIcon checkmarkIcon_; + + QColor iconColor_ = QColor("#999"); + + StatusIndicatorState state_ = StatusIndicatorState::Empty; + + static constexpr int MaxWidth = 24; +}; + +class TextLabel : public QTextBrowser +{ + Q_OBJECT + +public: + TextLabel(const QString &text, QWidget *parent = 0) + : QTextBrowser(parent) + { + setText(text); + setOpenExternalLinks(true); + + // Make it look and feel like an ordinary label. + setReadOnly(true); + setFrameStyle(QFrame::NoFrame); + QPalette pal = palette(); + pal.setColor(QPalette::Base, Qt::transparent); + setPalette(pal); + + // Wrap anywhere but prefer words, adjust minimum height on the fly. + setLineWrapMode(QTextEdit::WidgetWidth); + setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + connect(document()->documentLayout(), + &QAbstractTextDocumentLayout::documentSizeChanged, + this, + &TextLabel::adjustHeight); + document()->setDocumentMargin(0); + + setFixedHeight(20); + } + + void wheelEvent(QWheelEvent *event) override { event->ignore(); } + +private slots: + void adjustHeight(const QSizeF &size) { setFixedHeight(size.height()); } +}; + +class UserProfileFilter : public QObject +{ + Q_OBJECT + +public: + explicit UserProfileFilter(const QString &user_id, QLabel *parent) + : QObject(parent) + , user_id_{user_id} + {} + +signals: + void hoverOff(); + void hoverOn(); + +protected: + bool eventFilter(QObject *obj, QEvent *event) + { + if (event->type() == QEvent::MouseButtonRelease) { + // QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event); + // TODO: Open user profile + return true; + } else if (event->type() == QEvent::HoverLeave) { + emit hoverOff(); + return true; + } else if (event->type() == QEvent::HoverEnter) { + emit hoverOn(); + return true; + } + + return QObject::eventFilter(obj, event); + } + +private: + QString user_id_; +}; + +class TimelineItem : public QWidget +{ + Q_OBJECT +public: + TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice> &e, + bool with_sender, + const QString &room_id, + QWidget *parent = 0); + TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text> &e, + bool with_sender, + const QString &room_id, + QWidget *parent = 0); + TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote> &e, + bool with_sender, + const QString &room_id, + QWidget *parent = 0); + + // For local messages. + // m.text & m.emote + TimelineItem(mtx::events::MessageType ty, + const QString &userid, + QString body, + bool withSender, + const QString &room_id, + QWidget *parent = 0); + // m.image + TimelineItem(ImageItem *item, + const QString &userid, + bool withSender, + const QString &room_id, + QWidget *parent = 0); + TimelineItem(FileItem *item, + const QString &userid, + bool withSender, + const QString &room_id, + QWidget *parent = 0); + TimelineItem(AudioItem *item, + const QString &userid, + bool withSender, + const QString &room_id, + QWidget *parent = 0); + TimelineItem(VideoItem *item, + const QString &userid, + bool withSender, + const QString &room_id, + QWidget *parent = 0); + + TimelineItem(ImageItem *img, + const mtx::events::RoomEvent<mtx::events::msg::Image> &e, + bool with_sender, + const QString &room_id, + QWidget *parent); + TimelineItem(StickerItem *img, + const mtx::events::Sticker &e, + bool with_sender, + const QString &room_id, + QWidget *parent); + TimelineItem(FileItem *file, + const mtx::events::RoomEvent<mtx::events::msg::File> &e, + bool with_sender, + const QString &room_id, + QWidget *parent); + TimelineItem(AudioItem *audio, + const mtx::events::RoomEvent<mtx::events::msg::Audio> &e, + bool with_sender, + const QString &room_id, + QWidget *parent); + TimelineItem(VideoItem *video, + const mtx::events::RoomEvent<mtx::events::msg::Video> &e, + bool with_sender, + const QString &room_id, + QWidget *parent); + + void setUserAvatar(const QImage &pixmap); + DescInfo descriptionMessage() const { return descriptionMsg_; } + QString eventId() const { return event_id_; } + void setEventId(const QString &event_id) { event_id_ = event_id; } + void markReceived(bool isEncrypted); + void markSent(); + bool isReceived() { return isReceived_; }; + void setRoomId(QString room_id) { room_id_ = room_id; } + void sendReadReceipt() const; + + //! Add a user avatar for this event. + void addAvatar(); + void addKeyRequestAction(); + +signals: + void eventRedacted(const QString &event_id); + void redactionFailed(const QString &msg); + +protected: + void paintEvent(QPaintEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; + +private: + void init(); + //! Add a context menu option to save the image of the timeline item. + void addSaveImageAction(ImageItem *image); + //! Add the reply action in the context menu for widgets that support it. + void addReplyAction(); + + template<class Widget> + void setupLocalWidgetLayout(Widget *widget, const QString &userid, bool withSender); + + template<class Event, class Widget> + void setupWidgetLayout(Widget *widget, const Event &event, bool withSender); + + void generateBody(const QString &body); + void generateBody(const QString &user_id, const QString &displayname, const QString &body); + void generateTimestamp(const QDateTime &time); + + void setupAvatarLayout(const QString &userName); + void setupSimpleLayout(); + + void adjustMessageLayout(); + void adjustMessageLayoutForWidget(); + + //! Whether or not the event associated with the widget + //! has been acknowledged by the server. + bool isReceived_ = false; + + QString replaceEmoji(const QString &body); + QString event_id_; + QString room_id_; + + DescInfo descriptionMsg_; + + QMenu *contextMenu_; + QAction *showReadReceipts_; + QAction *markAsRead_; + QAction *redactMsg_; + QAction *replyMsg_; + + QHBoxLayout *topLayout_ = nullptr; + QHBoxLayout *messageLayout_ = nullptr; + QVBoxLayout *mainLayout_ = nullptr; + QHBoxLayout *widgetLayout_ = nullptr; + + Avatar *userAvatar_; + + QFont font_; + QFont usernameFont_; + + StatusIndicator *statusIndicator_; + + QLabel *timestamp_; + QLabel *userName_; + TextLabel *body_; +}; + +template<class Widget> +void +TimelineItem::setupLocalWidgetLayout(Widget *widget, const QString &userid, bool withSender) +{ + auto displayName = Cache::displayName(room_id_, userid); + auto timestamp = QDateTime::currentDateTime(); + + descriptionMsg_ = {"You", + userid, + QString(" %1").arg(utils::messageDescription<Widget>()), + utils::descriptiveTime(timestamp), + timestamp}; + + generateTimestamp(timestamp); + + widgetLayout_ = new QHBoxLayout; + widgetLayout_->setContentsMargins(0, 2, 0, 2); + widgetLayout_->addWidget(widget); + widgetLayout_->addStretch(1); + + if (withSender) { + generateBody(userid, displayName, ""); + setupAvatarLayout(displayName); + + AvatarProvider::resolve( + room_id_, userid, this, [this](const QImage &img) { setUserAvatar(img); }); + } else { + setupSimpleLayout(); + } + + adjustMessageLayoutForWidget(); +} + +template<class Event, class Widget> +void +TimelineItem::setupWidgetLayout(Widget *widget, const Event &event, bool withSender) +{ + init(); + + event_id_ = QString::fromStdString(event.event_id); + const auto sender = QString::fromStdString(event.sender); + + auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts); + auto displayName = Cache::displayName(room_id_, sender); + + QSettings settings; + descriptionMsg_ = {sender == settings.value("auth/user_id") ? "You" : displayName, + sender, + QString(" %1").arg(utils::messageDescription<Widget>()), + utils::descriptiveTime(timestamp), + timestamp}; + + generateTimestamp(timestamp); + + widgetLayout_ = new QHBoxLayout(); + widgetLayout_->setContentsMargins(0, 2, 0, 2); + widgetLayout_->addWidget(widget); + widgetLayout_->addStretch(1); + + if (withSender) { + generateBody(sender, displayName, ""); + setupAvatarLayout(displayName); + + AvatarProvider::resolve( + room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); }); + } else { + setupSimpleLayout(); + } + + adjustMessageLayoutForWidget(); +}