summary refs log tree commit diff
path: root/include/timeline
diff options
context:
space:
mode:
authorKonstantinos Sideris <sideris.konstantin@gmail.com>2017-11-30 13:53:28 +0200
committerKonstantinos Sideris <sideris.konstantin@gmail.com>2017-11-30 13:53:28 +0200
commit32c83405771b2f7a751783529d17e1b84dad4224 (patch)
tree70b51c8f019bfcaae99207fba8d0214e4b69e94d /include/timeline
parentUse templates for the TimelineItem generation (diff)
downloadnheko-32c83405771b2f7a751783529d17e1b84dad4224.tar.xz
Create directories for related files
Diffstat (limited to 'include/timeline')
-rw-r--r--include/timeline/TimelineItem.h155
-rw-r--r--include/timeline/TimelineView.h310
-rw-r--r--include/timeline/TimelineViewManager.h80
-rw-r--r--include/timeline/widgets/FileItem.h98
-rw-r--r--include/timeline/widgets/ImageItem.h78
5 files changed, 721 insertions, 0 deletions
diff --git a/include/timeline/TimelineItem.h b/include/timeline/TimelineItem.h
new file mode 100644

index 00000000..9646405c --- /dev/null +++ b/include/timeline/TimelineItem.h
@@ -0,0 +1,155 @@ +/* + * 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 <QDateTime> +#include <QHBoxLayout> +#include <QLabel> +#include <QPainter> +#include <QStyle> +#include <QStyleOption> + +#include "AvatarProvider.h" +#include "Emote.h" +#include "File.h" +#include "Image.h" +#include "MessageEvent.h" +#include "Notice.h" +#include "RoomInfoListItem.h" +#include "Text.h" +#include "TimelineViewManager.h" + +class ImageItem; +class FileItem; +class Avatar; + +namespace events = matrix::events; +namespace msgs = matrix::events::messages; + +class TimelineItem : public QWidget +{ + Q_OBJECT +public: + TimelineItem(const events::MessageEvent<msgs::Notice> &e, + bool with_sender, + QWidget *parent = 0); + TimelineItem(const events::MessageEvent<msgs::Text> &e, + bool with_sender, + QWidget *parent = 0); + TimelineItem(const events::MessageEvent<msgs::Emote> &e, + bool with_sender, + QWidget *parent = 0); + + // For local messages. + // m.text & m.emote + TimelineItem(events::MessageEventType ty, + const QString &userid, + QString body, + bool withSender, + QWidget *parent = 0); + // m.image + TimelineItem(ImageItem *item, const QString &userid, bool withSender, QWidget *parent = 0); + TimelineItem(FileItem *item, const QString &userid, bool withSender, QWidget *parent = 0); + + TimelineItem(ImageItem *img, + const events::MessageEvent<msgs::Image> &e, + bool with_sender, + QWidget *parent); + TimelineItem(FileItem *file, + const events::MessageEvent<msgs::File> &e, + bool with_sender, + QWidget *parent); + + void setUserAvatar(const QImage &pixmap); + DescInfo descriptionMessage() const { return descriptionMsg_; } + QString eventId() const { return event_id_; } + + ~TimelineItem(); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + void init(); + + template<class Widget> + void setupLocalWidgetLayout(Widget *widget, + const QString &userid, + const QString &msgDescription, + bool withSender); + + void generateBody(const QString &body); + void generateBody(const QString &userid, const QString &body); + void generateTimestamp(const QDateTime &time); + QString descriptiveTime(const QDateTime &then); + + void setupAvatarLayout(const QString &userName); + void setupSimpleLayout(); + + QString replaceEmoji(const QString &body); + QString event_id_; + + DescInfo descriptionMsg_; + + QHBoxLayout *topLayout_; + QVBoxLayout *sideLayout_; // Avatar or Timestamp + QVBoxLayout *mainLayout_; // Header & Message body + + QHBoxLayout *headerLayout_; // Username (&) Timestamp + + Avatar *userAvatar_; + + QFont font_; + + QLabel *timestamp_; + QLabel *userName_; + QLabel *body_; +}; + +template<class Widget> +void +TimelineItem::setupLocalWidgetLayout(Widget *widget, + const QString &userid, + const QString &msgDescription, + bool withSender) +{ + auto displayName = TimelineViewManager::displayName(userid); + auto timestamp = QDateTime::currentDateTime(); + + descriptionMsg_ = { + "You", userid, QString(" %1").arg(msgDescription), descriptiveTime(timestamp)}; + + generateTimestamp(timestamp); + + auto widgetLayout = new QHBoxLayout(); + widgetLayout->setContentsMargins(0, 5, 0, 0); + widgetLayout->addWidget(widget); + widgetLayout->addStretch(1); + + if (withSender) { + generateBody(displayName, ""); + setupAvatarLayout(displayName); + mainLayout_->addLayout(headerLayout_); + + AvatarProvider::resolve(userid, this); + } else { + setupSimpleLayout(); + } + + mainLayout_->addLayout(widgetLayout); +} diff --git a/include/timeline/TimelineView.h b/include/timeline/TimelineView.h new file mode 100644
index 00000000..898a304e --- /dev/null +++ b/include/timeline/TimelineView.h
@@ -0,0 +1,310 @@ +/* + * 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 <QApplication> +#include <QDebug> +#include <QLayout> +#include <QList> +#include <QQueue> +#include <QScrollArea> +#include <QSettings> +#include <QStyle> +#include <QStyleOption> + +#include "Emote.h" +#include "File.h" +#include "Image.h" +#include "MatrixClient.h" +#include "MessageEvent.h" +#include "Notice.h" +#include "Text.h" +#include "TimelineItem.h" + +class FloatingButton; +class RoomMessages; +class ScrollBar; +class Timeline; +struct DescInfo; + +namespace msgs = matrix::events::messages; +namespace events = matrix::events; + +// Contains info about a message shown in the history view +// but not yet confirmed by the homeserver through sync. +struct PendingMessage +{ + matrix::events::MessageEventType ty; + int txn_id; + QString body; + QString filename; + QString event_id; + TimelineItem *widget; + + PendingMessage(matrix::events::MessageEventType ty, + int txn_id, + QString body, + QString filename, + QString event_id, + TimelineItem *widget) + : ty(ty) + , txn_id(txn_id) + , body(body) + , filename(filename) + , event_id(event_id) + , widget(widget) + {} +}; + +// In which place new TimelineItems should be inserted. +enum class TimelineDirection +{ + Top, + Bottom, +}; + +class TimelineView : public QWidget +{ + Q_OBJECT + +public: + TimelineView(const Timeline &timeline, + QSharedPointer<MatrixClient> client, + const QString &room_id, + QWidget *parent = 0); + TimelineView(QSharedPointer<MatrixClient> client, + const QString &room_id, + QWidget *parent = 0); + + // Add new events at the end of the timeline. + int addEvents(const Timeline &timeline); + void addUserMessage(matrix::events::MessageEventType ty, const QString &msg); + + template<class Widget, events::MessageEventType MsgType> + void addUserMessage(const QString &url, const QString &filename); + void updatePendingMessage(int txn_id, QString event_id); + void scrollDown(); + +public slots: + void sliderRangeChanged(int min, int max); + void sliderMoved(int position); + void fetchHistory(); + + // Add old events at the top of the timeline. + void addBackwardsEvents(const QString &room_id, const RoomMessages &msgs); + + // Whether or not the initial batch has been loaded. + bool hasLoaded() { return scroll_layout_->count() > 1 || isTimelineFinished; } + + void handleFailedMessage(int txnid); + +private slots: + void sendNextPendingMessage(); + +signals: + void updateLastTimelineMessage(const QString &user, const DescInfo &info); + void clearUnreadMessageCount(const QString &room_id); + +protected: + void paintEvent(QPaintEvent *event) override; + void showEvent(QShowEvent *event) override; + bool event(QEvent *event) override; + +private: + void init(); + void addTimelineItem(TimelineItem *item, TimelineDirection direction); + void updateLastSender(const QString &user_id, TimelineDirection direction); + void notifyForLastEvent(); + void readLastEvent() const; + QString getLastEventId() const; + + template<class Event, class Widget> + TimelineItem *processMessageEvent(const QJsonObject &event, TimelineDirection direction); + + // TODO: Remove this eventually. + template<class Event> + TimelineItem *processMessageEvent(const QJsonObject &event, TimelineDirection direction); + + // For events with custom display widgets. + template<class Event, class Widget> + TimelineItem *createTimelineItem(const Event &event, bool withSender); + + // For events without custom display widgets. + // TODO: All events should have custom widgets. + template<class Event> + TimelineItem *createTimelineItem(const Event &event, bool withSender); + + // Used to determine whether or not we should prefix a message with the + // sender's name. + bool isSenderRendered(const QString &user_id, TimelineDirection direction); + + bool isPendingMessage(const QString &txnid, const QString &sender, const QString &userid); + void removePendingMessage(const QString &txnid); + + bool isDuplicate(const QString &event_id) { return eventIds_.contains(event_id); } + + void handleNewUserMessage(PendingMessage msg); + + // Return nullptr if the event couldn't be parsed. + TimelineItem *parseMessageEvent(const QJsonObject &event, TimelineDirection direction); + + QVBoxLayout *top_layout_; + QVBoxLayout *scroll_layout_; + + QScrollArea *scroll_area_; + ScrollBar *scrollbar_; + QWidget *scroll_widget_; + + QString lastSender_; + QString firstSender_; + QString room_id_; + QString prev_batch_token_; + QString local_user_; + + bool isPaginationInProgress_ = false; + + // Keeps track whether or not the user has visited the view. + bool isInitialized = false; + bool isTimelineFinished = false; + bool isInitialSync = true; + + const int SCROLL_BAR_GAP = 200; + + QTimer *paginationTimer_; + + int scroll_height_ = 0; + int previous_max_height_ = 0; + + int oldPosition_; + int oldHeight_; + + FloatingButton *scrollDownBtn_; + + TimelineDirection lastMessageDirection_; + + // The events currently rendered. Used for duplicate detection. + QMap<QString, bool> eventIds_; + QQueue<PendingMessage> pending_msgs_; + QList<PendingMessage> pending_sent_msgs_; + QSharedPointer<MatrixClient> client_; +}; + +template<class Widget, events::MessageEventType MsgType> +void +TimelineView::addUserMessage(const QString &url, const QString &filename) +{ + QSettings settings; + auto user_id = settings.value("auth/user_id").toString(); + auto with_sender = lastSender_ != user_id; + + auto widget = new Widget(client_, url, filename, this); + + TimelineItem *view_item = new TimelineItem(widget, user_id, with_sender, scroll_widget_); + scroll_layout_->addWidget(view_item); + + lastMessageDirection_ = TimelineDirection::Bottom; + + QApplication::processEvents(); + + lastSender_ = user_id; + + int txn_id = client_->incrementTransactionId(); + + PendingMessage message(MsgType, txn_id, url, filename, "", view_item); + handleNewUserMessage(message); +} + +template<class Event> +TimelineItem * +TimelineView::createTimelineItem(const Event &event, bool withSender) +{ + TimelineItem *item = new TimelineItem(event, withSender, scroll_widget_); + return item; +} + +template<class Event, class Widget> +TimelineItem * +TimelineView::createTimelineItem(const Event &event, bool withSender) +{ + auto eventWidget = new Widget(client_, event); + auto item = new TimelineItem(eventWidget, event, withSender, scroll_widget_); + + return item; +} + +template<class Event> +TimelineItem * +TimelineView::processMessageEvent(const QJsonObject &data, TimelineDirection direction) +{ + Event event; + + try { + event.deserialize(data); + } catch (const DeserializationException &e) { + qWarning() << e.what() << data; + return nullptr; + } + + if (isDuplicate(event.eventId())) + return nullptr; + + eventIds_[event.eventId()] = true; + + QString txnid = event.unsignedData().transactionId(); + if (!txnid.isEmpty() && isPendingMessage(txnid, event.sender(), local_user_)) { + removePendingMessage(txnid); + return nullptr; + } + + auto with_sender = isSenderRendered(event.sender(), direction); + + updateLastSender(event.sender(), direction); + + return createTimelineItem<Event>(event, with_sender); +} + +template<class Event, class Widget> +TimelineItem * +TimelineView::processMessageEvent(const QJsonObject &data, TimelineDirection direction) +{ + Event event; + + try { + event.deserialize(data); + } catch (const DeserializationException &e) { + qWarning() << e.what() << data; + return nullptr; + } + + if (isDuplicate(event.eventId())) + return nullptr; + + eventIds_[event.eventId()] = true; + + QString txnid = event.unsignedData().transactionId(); + if (!txnid.isEmpty() && isPendingMessage(txnid, event.sender(), local_user_)) { + removePendingMessage(txnid); + return nullptr; + } + + auto with_sender = isSenderRendered(event.sender(), direction); + + updateLastSender(event.sender(), direction); + + return createTimelineItem<Event, Widget>(event, with_sender); +} diff --git a/include/timeline/TimelineViewManager.h b/include/timeline/TimelineViewManager.h new file mode 100644
index 00000000..854c2550 --- /dev/null +++ b/include/timeline/TimelineViewManager.h
@@ -0,0 +1,80 @@ +/* + * 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 <QMap> +#include <QSharedPointer> +#include <QStackedWidget> + +#include "MessageEvent.h" + +class JoinedRoom; +class MatrixClient; +class RoomInfoListItem; +class Rooms; +class TimelineView; +struct DescInfo; + +class TimelineViewManager : public QStackedWidget +{ + Q_OBJECT + +public: + TimelineViewManager(QSharedPointer<MatrixClient> client, QWidget *parent); + ~TimelineViewManager(); + + // Initialize with timeline events. + void initialize(const Rooms &rooms); + // Empty initialization. + void initialize(const QList<QString> &rooms); + + void addRoom(const JoinedRoom &room, const QString &room_id); + void addRoom(const QString &room_id); + + void sync(const Rooms &rooms); + void clearAll(); + + // Check if all the timelines have been loaded. + bool hasLoaded() const; + + static QString chooseRandomColor(); + static QString displayName(const QString &userid); + + static QMap<QString, QString> DISPLAY_NAMES; + +signals: + void clearRoomMessageCount(QString roomid); + void unreadMessages(QString roomid, int count); + void updateRoomsLastMessage(const QString &user, const DescInfo &info); + +public slots: + void setHistoryView(const QString &room_id); + void queueTextMessage(const QString &msg); + void queueEmoteMessage(const QString &msg); + void queueImageMessage(const QString &roomid, const QString &filename, const QString &url); + void queueFileMessage(const QString &roomid, const QString &filename, const QString &url); + +private slots: + void messageSent(const QString &eventid, const QString &roomid, int txnid); + void messageSendFailed(const QString &roomid, int txnid); + +private: + QString active_room_; + QMap<QString, QSharedPointer<TimelineView>> views_; + QSharedPointer<MatrixClient> client_; +}; diff --git a/include/timeline/widgets/FileItem.h b/include/timeline/widgets/FileItem.h new file mode 100644
index 00000000..ebb18111 --- /dev/null +++ b/include/timeline/widgets/FileItem.h
@@ -0,0 +1,98 @@ +/* + * 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 <QEvent> +#include <QIcon> +#include <QMouseEvent> +#include <QSharedPointer> +#include <QWidget> + +#include "File.h" +#include "MatrixClient.h" +#include "MessageEvent.h" + +namespace events = matrix::events; +namespace msgs = matrix::events::messages; + +constexpr int MaxWidth = 400; +constexpr int Height = 70; +constexpr int IconRadius = 22; +constexpr int IconDiameter = IconRadius * 2; +constexpr int HorizontalPadding = 12; +constexpr int TextPadding = 15; +constexpr int DownloadIconRadius = IconRadius - 4; + +constexpr double VerticalPadding = Height - 2 * IconRadius; +constexpr double IconYCenter = Height / 2; +constexpr double IconXCenter = HorizontalPadding + IconRadius; + +class FileItem : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor) + Q_PROPERTY(QColor iconColor WRITE setIconColor READ iconColor) + Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor) + +public: + FileItem(QSharedPointer<MatrixClient> client, + const events::MessageEvent<msgs::File> &event, + QWidget *parent = nullptr); + + FileItem(QSharedPointer<MatrixClient> client, + const QString &url, + const QString &filename, + QWidget *parent = nullptr); + + QSize sizeHint() const override; + + void setTextColor(const QColor &color) { textColor_ = color; } + void setIconColor(const QColor &color) { iconColor_ = color; } + void setBackgroundColor(const QColor &color) { backgroundColor_ = color; } + + QColor textColor() const { return textColor_; } + QColor iconColor() const { return iconColor_; } + QColor backgroundColor() const { return backgroundColor_; } + +protected: + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + +private slots: + void fileDownloaded(const QString &event_id, const QByteArray &data); + +private: + QString calculateFileSize(int nbytes) const; + void openUrl(); + void init(); + + QUrl url_; + QString text_; + QString readableFileSize_; + QString filenameToSave_; + + events::MessageEvent<msgs::File> event_; + QSharedPointer<MatrixClient> client_; + + QIcon icon_; + + QColor textColor_ = QColor("white"); + QColor iconColor_ = QColor("#38A3D8"); + QColor backgroundColor_ = QColor("#333"); +}; diff --git a/include/timeline/widgets/ImageItem.h b/include/timeline/widgets/ImageItem.h new file mode 100644
index 00000000..c4f6998a --- /dev/null +++ b/include/timeline/widgets/ImageItem.h
@@ -0,0 +1,78 @@ +/* + * 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 <QEvent> +#include <QMouseEvent> +#include <QSharedPointer> +#include <QWidget> + +#include "Image.h" +#include "MatrixClient.h" +#include "MessageEvent.h" + +namespace events = matrix::events; +namespace msgs = matrix::events::messages; + +class ImageItem : public QWidget +{ + Q_OBJECT +public: + ImageItem(QSharedPointer<MatrixClient> client, + const events::MessageEvent<msgs::Image> &event, + QWidget *parent = nullptr); + + ImageItem(QSharedPointer<MatrixClient> client, + const QString &url, + const QString &filename, + QWidget *parent = nullptr); + + void setImage(const QPixmap &image); + + QSize sizeHint() const override; + +protected: + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + +private slots: + void imageDownloaded(const QString &event_id, const QPixmap &img); + +private: + void scaleImage(); + void openUrl(); + + int max_width_ = 500; + int max_height_ = 300; + + int width_; + int height_; + + QPixmap scaled_image_; + QPixmap image_; + + QUrl url_; + QString text_; + + int bottom_height_ = 30; + + events::MessageEvent<msgs::Image> event_; + + QSharedPointer<MatrixClient> client_; +};