From edff71bc2403c966bd61d5dde391184aa8822aac Mon Sep 17 00:00:00 2001 From: Konstantinos Sideris Date: Sun, 10 Sep 2017 12:58:00 +0300 Subject: [PATCH] Implement image uploads (#24) --- CMakeLists.txt | 4 +- include/ImageItem.h | 53 ++--- include/LoginPage.h | 84 ++++---- include/MainWindow.h | 76 +++---- include/MatrixClient.h | 12 +- include/TextInputWidget.h | 18 +- include/TimelineItem.h | 3 + include/TimelineView.h | 40 +--- include/TimelineViewManager.h | 1 + include/ui/CircularProgress.h | 120 ----------- include/ui/LoadingIndicator.h | 49 +++++ src/ChatPage.cc | 12 ++ src/ImageItem.cc | 210 ++++++++++--------- src/LoginPage.cc | 369 +++++++++++++++++----------------- src/MainWindow.cc | 262 ++++++++++++------------ src/MatrixClient.cc | 109 ++++++++-- src/TextInputWidget.cc | 106 +++++++--- src/TimelineItem.cc | 33 +++ src/TimelineView.cc | 67 +++++- src/TimelineViewManager.cc | 18 ++ src/ui/CircularProgress.cc | 201 ------------------ src/ui/LoadingIndicator.cc | 86 ++++++++ 22 files changed, 1017 insertions(+), 916 deletions(-) delete mode 100644 include/ui/CircularProgress.h create mode 100644 include/ui/LoadingIndicator.h delete mode 100644 src/ui/CircularProgress.cc create mode 100644 src/ui/LoadingIndicator.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 926448df..5cbdc797 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,7 @@ set(SRC_FILES src/ui/Avatar.cc src/ui/Badge.cc - src/ui/CircularProgress.cc + src/ui/LoadingIndicator.cc src/ui/FlatButton.cc src/ui/OverlayModal.cc src/ui/ScrollBar.cc @@ -217,7 +217,7 @@ qt5_wrap_cpp(MOC_HEADERS include/ui/Avatar.h include/ui/Badge.h - include/ui/CircularProgress.h + include/ui/LoadingIndicator.h include/ui/FlatButton.h include/ui/OverlayWidget.h include/ui/ScrollBar.h diff --git a/include/ImageItem.h b/include/ImageItem.h index 5953c6e5..20e0772d 100644 --- a/include/ImageItem.h +++ b/include/ImageItem.h @@ -26,47 +26,52 @@ #include "MatrixClient.h" namespace events = matrix::events; -namespace msgs = matrix::events::messages; +namespace msgs = matrix::events::messages; class ImageItem : public QWidget { - Q_OBJECT + Q_OBJECT public: - ImageItem(QSharedPointer client, - const events::MessageEvent &event, - QWidget *parent = nullptr); + ImageItem(QSharedPointer client, + const events::MessageEvent &event, + QWidget *parent = nullptr); - void setImage(const QPixmap &image); + ImageItem(QSharedPointer client, + const QString &url, + const QString &filename, + QWidget *parent = nullptr); - QSize sizeHint() const override; + 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; + 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); + void imageDownloaded(const QString &event_id, const QPixmap &img); private: - void scaleImage(); - void openUrl(); + void scaleImage(); + void openUrl(); - int max_width_ = 500; - int max_height_ = 300; + int max_width_ = 500; + int max_height_ = 300; - int width_; - int height_; + int width_; + int height_; - QPixmap scaled_image_; - QPixmap image_; + QPixmap scaled_image_; + QPixmap image_; - QUrl url_; - QString text_; + QUrl url_; + QString text_; - int bottom_height_ = 30; + int bottom_height_ = 30; - events::MessageEvent event_; + events::MessageEvent event_; - QSharedPointer client_; + QSharedPointer client_; }; diff --git a/include/LoginPage.h b/include/LoginPage.h index ef0a6e9e..5caa3f1e 100644 --- a/include/LoginPage.h +++ b/include/LoginPage.h @@ -23,8 +23,8 @@ #include #include -#include "CircularProgress.h" #include "FlatButton.h" +#include "LoadingIndicator.h" #include "MatrixClient.h" #include "OverlayModal.h" #include "RaisedButton.h" @@ -32,68 +32,68 @@ class LoginPage : public QWidget { - Q_OBJECT + Q_OBJECT public: - LoginPage(QSharedPointer client, QWidget *parent = 0); - ~LoginPage(); + LoginPage(QSharedPointer client, QWidget *parent = 0); + ~LoginPage(); - void reset(); + void reset(); signals: - void backButtonClicked(); + void backButtonClicked(); private slots: - // Callback for the back button. - void onBackButtonClicked(); + // Callback for the back button. + void onBackButtonClicked(); - // Callback for the login button. - void onLoginButtonClicked(); + // Callback for the login button. + void onLoginButtonClicked(); - // Callback for probing the server found in the mxid - void onMatrixIdEntered(); + // Callback for probing the server found in the mxid + void onMatrixIdEntered(); - // Callback for probing the manually entered server - void onServerAddressEntered(); + // Callback for probing the manually entered server + void onServerAddressEntered(); - // Displays errors produced during the login. - void loginError(QString error_message); + // Displays errors produced during the login. + void loginError(QString error_message); - // Callback for errors produced during server probing - void versionError(QString error_message); + // Callback for errors produced during server probing + void versionError(QString error_message); - // Callback for successful server probing - void versionSuccess(); + // Callback for successful server probing + void versionSuccess(); private: - bool isMatrixIdValid(); + bool isMatrixIdValid(); - QVBoxLayout *top_layout_; + QVBoxLayout *top_layout_; - QHBoxLayout *top_bar_layout_; - QHBoxLayout *logo_layout_; - QHBoxLayout *button_layout_; + QHBoxLayout *top_bar_layout_; + QHBoxLayout *logo_layout_; + QHBoxLayout *button_layout_; - QLabel *logo_; - QLabel *error_label_; + QLabel *logo_; + QLabel *error_label_; - QHBoxLayout *serverLayout_; - QHBoxLayout *matrixidLayout_; - CircularProgress *spinner_; - QLabel *errorIcon_; - QString inferredServerAddress_; + QHBoxLayout *serverLayout_; + QHBoxLayout *matrixidLayout_; + LoadingIndicator *spinner_; + QLabel *errorIcon_; + QString inferredServerAddress_; - FlatButton *back_button_; - RaisedButton *login_button_; + FlatButton *back_button_; + RaisedButton *login_button_; - QWidget *form_widget_; - QHBoxLayout *form_wrapper_; - QVBoxLayout *form_layout_; + QWidget *form_widget_; + QHBoxLayout *form_wrapper_; + QVBoxLayout *form_layout_; - TextField *matrixid_input_; - TextField *password_input_; - TextField *serverInput_; + TextField *matrixid_input_; + TextField *password_input_; + TextField *serverInput_; - // Matrix client API provider. - QSharedPointer client_; + // Matrix client API provider. + QSharedPointer client_; }; diff --git a/include/MainWindow.h b/include/MainWindow.h index a8a86719..de535d35 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -21,7 +21,7 @@ #include #include "ChatPage.h" -#include "CircularProgress.h" +#include "LoadingIndicator.h" #include "LoginPage.h" #include "MatrixClient.h" #include "OverlayModal.h" @@ -32,64 +32,64 @@ class MainWindow : public QMainWindow { - Q_OBJECT + Q_OBJECT public: - explicit MainWindow(QWidget *parent = 0); - ~MainWindow(); + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); - static MainWindow *instance(); - void saveCurrentWindowSize(); + static MainWindow *instance(); + void saveCurrentWindowSize(); protected: - void closeEvent(QCloseEvent *event); + void closeEvent(QCloseEvent *event); private slots: - // Handle interaction with the tray icon. - void iconActivated(QSystemTrayIcon::ActivationReason reason); + // Handle interaction with the tray icon. + void iconActivated(QSystemTrayIcon::ActivationReason reason); - // Show the welcome page in the main window. - void showWelcomePage(); + // Show the welcome page in the main window. + void showWelcomePage(); - // Show the login page in the main window. - void showLoginPage(); + // Show the login page in the main window. + void showLoginPage(); - // Show the register page in the main window. - void showRegisterPage(); + // Show the register page in the main window. + void showRegisterPage(); - // Show the chat page and start communicating with the given access token. - void showChatPage(QString user_id, QString home_server, QString token); + // Show the chat page and start communicating with the given access token. + void showChatPage(QString user_id, QString home_server, QString token); - void removeOverlayProgressBar(); + void removeOverlayProgressBar(); private: - bool hasActiveUser(); - void restoreWindowSize(); + bool hasActiveUser(); + void restoreWindowSize(); - static MainWindow *instance_; + static MainWindow *instance_; - // The initial welcome screen. - WelcomePage *welcome_page_; + // The initial welcome screen. + WelcomePage *welcome_page_; - // The login screen. - LoginPage *login_page_; + // The login screen. + LoginPage *login_page_; - // The register page. - RegisterPage *register_page_; + // The register page. + RegisterPage *register_page_; - // A stacked widget that handles the transitions between widgets. - SlidingStackWidget *sliding_stack_; + // A stacked widget that handles the transitions between widgets. + SlidingStackWidget *sliding_stack_; - // The main chat area. - ChatPage *chat_page_; + // The main chat area. + ChatPage *chat_page_; - // Used to hide undefined states between page transitions. - OverlayModal *progress_modal_; - CircularProgress *spinner_; + // Used to hide undefined states between page transitions. + OverlayModal *progress_modal_; + LoadingIndicator *spinner_; - // Matrix Client API provider. - QSharedPointer client_; + // Matrix Client API provider. + QSharedPointer client_; - // Tray icon that shows the unread message count. - TrayIcon *trayIcon_; + // Tray icon that shows the unread message count. + TrayIcon *trayIcon_; }; diff --git a/include/MatrixClient.h b/include/MatrixClient.h index 58b24f9b..8d6c60a7 100644 --- a/include/MatrixClient.h +++ b/include/MatrixClient.h @@ -39,7 +39,8 @@ public: void sync() noexcept; void sendRoomMessage(matrix::events::MessageEventType ty, const QString &roomid, - const QString &msg) noexcept; + const QString &msg, + const QString &url = "") noexcept; void login(const QString &username, const QString &password) noexcept; void registerUser(const QString &username, const QString &password, @@ -50,6 +51,7 @@ public: void fetchOwnAvatar(const QUrl &avatar_url); void downloadImage(const QString &event_id, const QUrl &url); void messages(const QString &room_id, const QString &from_token, int limit = 20) noexcept; + void uploadImage(const QString &roomid, const QString &filename); inline QUrl getHomeServer(); inline int transactionId(); @@ -77,6 +79,7 @@ signals: const QString &homeserver, const QString &token); void versionSuccess(); + void imageUploaded(const QString &roomid, const QString &filename, const QString &url); void roomAvatarRetrieved(const QString &roomid, const QPixmap &img); void userAvatarRetrieved(const QString &userId, const QImage &img); @@ -102,6 +105,7 @@ private: GetProfile, Image, InitialSync, + ImageUpload, Login, Logout, Messages, @@ -118,6 +122,7 @@ private: void onGetOwnProfileResponse(QNetworkReply *reply); void onImageResponse(QNetworkReply *reply); void onInitialSyncResponse(QNetworkReply *reply); + void onImageUploadResponse(QNetworkReply *reply); void onLoginResponse(QNetworkReply *reply); void onLogoutResponse(QNetworkReply *reply); void onMessagesResponse(QNetworkReply *reply); @@ -129,7 +134,10 @@ private: void onVersionsResponse(QNetworkReply *reply); // Client API prefix. - QString api_url_; + QString clientApiUrl_; + + // Media API prefix. + QString mediaApiUrl_; // The Matrix server used for communication. QUrl server_; diff --git a/include/TextInputWidget.h b/include/TextInputWidget.h index 73c2a603..732f4f61 100644 --- a/include/TextInputWidget.h +++ b/include/TextInputWidget.h @@ -24,6 +24,10 @@ #include "EmojiPickButton.h" #include "FlatButton.h" +#include "Image.h" +#include "LoadingIndicator.h" + +namespace msgs = matrix::events::messages; static const QString EMOTE_COMMAND("/me "); @@ -48,6 +52,8 @@ public: public slots: void onSendButtonClicked(); + void openFileSelection(); + void hideUploadSpinner(); inline void focusLineEdit(); private slots: @@ -56,16 +62,20 @@ private slots: signals: void sendTextMessage(QString msg); void sendEmoteMessage(QString msg); + void uploadImage(QString filename); private: + void showUploadSpinner(); QString parseEmoteCommand(const QString &cmd); - QHBoxLayout *top_layout_; + QHBoxLayout *topLayout_; FilteredTextEdit *input_; - FlatButton *send_file_button_; - FlatButton *send_message_button_; - EmojiPickButton *emoji_button_; + LoadingIndicator *spinner_; + + FlatButton *sendFileBtn_; + FlatButton *sendMessageBtn_; + EmojiPickButton *emojiBtn_; }; inline void diff --git a/include/TimelineItem.h b/include/TimelineItem.h index edc15dab..0a0538f9 100644 --- a/include/TimelineItem.h +++ b/include/TimelineItem.h @@ -50,11 +50,14 @@ public: 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(ImageItem *img, const events::MessageEvent &e, diff --git a/include/TimelineView.h b/include/TimelineView.h index 3ecf8ba7..61283fa2 100644 --- a/include/TimelineView.h +++ b/include/TimelineView.h @@ -85,6 +85,7 @@ public: // Add new events at the end of the timeline. int addEvents(const Timeline &timeline); void addUserMessage(matrix::events::MessageEventType ty, const QString &msg, int txn_id); + void addUserMessage(const QString &url, const QString &filename, int txn_id); void updatePendingMessage(int txn_id, QString event_id); void scrollDown(); @@ -108,11 +109,11 @@ private: // Used to determine whether or not we should prefix a message with the sender's name. bool isSenderRendered(const QString &user_id, TimelineDirection direction); - template - bool isPendingMessage(const events::MessageEvent &e, const QString &userid); - - template - void removePendingMessage(const events::MessageEvent &e); + bool isPendingMessage(const QString &eventid, + const QString &body, + const QString &sender, + const QString &userid); + void removePendingMessage(const QString &eventid, const QString &body); inline bool isDuplicate(const QString &event_id); @@ -159,32 +160,3 @@ TimelineView::isDuplicate(const QString &event_id) { return eventIds_.contains(event_id); } - -template -bool -TimelineView::isPendingMessage(const events::MessageEvent &e, const QString &local_userid) -{ - if (e.sender() != local_userid) - return false; - - for (const auto &msg : pending_msgs_) { - if (msg.event_id == e.eventId() || msg.body == e.content().body()) - return true; - } - - return false; -} - -template -void -TimelineView::removePendingMessage(const events::MessageEvent &e) -{ - for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); it++) { - int index = std::distance(pending_msgs_.begin(), it); - - if (it->event_id == e.eventId() || it->body == e.content().body()) { - pending_msgs_.removeAt(index); - break; - } - } -} diff --git a/include/TimelineViewManager.h b/include/TimelineViewManager.h index d3ca198e..14f47928 100644 --- a/include/TimelineViewManager.h +++ b/include/TimelineViewManager.h @@ -56,6 +56,7 @@ public slots: void setHistoryView(const QString &room_id); void sendTextMessage(const QString &msg); void sendEmoteMessage(const QString &msg); + void sendImageMessage(const QString &roomid, const QString &filename, const QString &url); private slots: void messageSent(const QString &eventid, const QString &roomid, int txnid); diff --git a/include/ui/CircularProgress.h b/include/ui/CircularProgress.h deleted file mode 100644 index 5ed8994d..00000000 --- a/include/ui/CircularProgress.h +++ /dev/null @@ -1,120 +0,0 @@ -#pragma once - -#include -#include - -#include "Theme.h" - -class CircularProgressDelegate; - -class CircularProgress : public QProgressBar -{ - Q_OBJECT - - Q_PROPERTY(qreal lineWidth WRITE setLineWidth READ lineWidth) - Q_PROPERTY(qreal size WRITE setSize READ size) - Q_PROPERTY(QColor color WRITE setColor READ color) - -public: - explicit CircularProgress(QWidget *parent = nullptr); - ~CircularProgress(); - - void setProgressType(ui::ProgressType type); - void setLineWidth(qreal width); - void setSize(int size); - void setColor(const QColor &color); - - ui::ProgressType progressType() const; - qreal lineWidth() const; - int size() const; - QColor color() const; - - QSize sizeHint() const override; - -protected: - void paintEvent(QPaintEvent *event) override; - -private: - CircularProgressDelegate *delegate_; - - ui::ProgressType progress_type_; - - QColor color_; - - // Circle width. - qreal width_; - - // Circle radius. - int size_; - - // Animation duration. - int duration_; -}; - -class CircularProgressDelegate : public QObject -{ - Q_OBJECT - - Q_PROPERTY(qreal dashOffset WRITE setDashOffset READ dashOffset) - Q_PROPERTY(qreal dashLength WRITE setDashLength READ dashLength) - Q_PROPERTY(int angle WRITE setAngle READ angle) - -public: - explicit CircularProgressDelegate(CircularProgress *parent); - ~CircularProgressDelegate(); - - inline void setDashOffset(qreal offset); - inline void setDashLength(qreal length); - inline void setAngle(int angle); - - inline qreal dashOffset() const; - inline qreal dashLength() const; - inline int angle() const; - -private: - CircularProgress *const progress_; - - qreal dash_offset_; - qreal dash_length_; - - int angle_; -}; - -inline void -CircularProgressDelegate::setDashOffset(qreal offset) -{ - dash_offset_ = offset; - progress_->update(); -} - -inline void -CircularProgressDelegate::setDashLength(qreal length) -{ - dash_length_ = length; - progress_->update(); -} - -inline void -CircularProgressDelegate::setAngle(int angle) -{ - angle_ = angle; - progress_->update(); -} - -inline qreal -CircularProgressDelegate::dashOffset() const -{ - return dash_offset_; -} - -inline qreal -CircularProgressDelegate::dashLength() const -{ - return dash_length_; -} - -inline int -CircularProgressDelegate::angle() const -{ - return angle_; -} diff --git a/include/ui/LoadingIndicator.h b/include/ui/LoadingIndicator.h new file mode 100644 index 00000000..2641955a --- /dev/null +++ b/include/ui/LoadingIndicator.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include +#include + +class LoadingIndicator : public QWidget +{ + Q_OBJECT + +public: + LoadingIndicator(QWidget *parent = 0); + virtual ~LoadingIndicator(); + + void paintEvent(QPaintEvent *e); + + void start(); + void stop(); + + QColor color() + { + return color_; + } + void setColor(QColor color) + { + color_ = color; + } + + int interval() + { + return interval_; + } + void setInterval(int interval) + { + interval_ = interval; + } + +private slots: + void onTimeout(); + +private: + int interval_; + int angle_; + + QColor color_; + QTimer *timer_; +}; diff --git a/src/ChatPage.cc b/src/ChatPage.cc index d393a65d..6bfbf400 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -153,6 +153,18 @@ ChatPage::ChatPage(QSharedPointer client, QWidget *parent) view_manager_, SLOT(sendEmoteMessage(const QString &))); + connect(text_input_, &TextInputWidget::uploadImage, this, [=](QString filename) { + client_->uploadImage(current_room_, filename); + }); + + connect(client_.data(), + &MatrixClient::imageUploaded, + this, + [=](QString roomid, QString filename, QString url) { + text_input_->hideUploadSpinner(); + view_manager_->sendImageMessage(roomid, filename, url); + }); + connect(client_.data(), SIGNAL(roomAvatarRetrieved(const QString &, const QPixmap &)), this, diff --git a/src/ImageItem.cc b/src/ImageItem.cc index 77523465..e84a2a9f 100644 --- a/src/ImageItem.cc +++ b/src/ImageItem.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -26,169 +27,198 @@ #include "ImageOverlayDialog.h" namespace events = matrix::events; -namespace msgs = matrix::events::messages; +namespace msgs = matrix::events::messages; ImageItem::ImageItem(QSharedPointer client, - const events::MessageEvent &event, - QWidget *parent) + const events::MessageEvent &event, + QWidget *parent) : QWidget(parent) , event_{ event } , client_{ client } { - setMouseTracking(true); - setCursor(Qt::PointingHandCursor); - setAttribute(Qt::WA_Hover, true); + setMouseTracking(true); + setCursor(Qt::PointingHandCursor); + setAttribute(Qt::WA_Hover, true); - url_ = event.msgContent().url(); - text_ = event.content().body(); + url_ = event.msgContent().url(); + text_ = event.content().body(); - QList url_parts = url_.toString().split("mxc://"); + QList url_parts = url_.toString().split("mxc://"); - if (url_parts.size() != 2) { - qDebug() << "Invalid format for image" << url_.toString(); - return; - } + if (url_parts.size() != 2) { + qDebug() << "Invalid format for image" << url_.toString(); + return; + } - QString media_params = url_parts[1]; - url_ = QString("%1/_matrix/media/r0/download/%2").arg(client_.data()->getHomeServer().toString(), media_params); + QString media_params = url_parts[1]; + url_ = QString("%1/_matrix/media/r0/download/%2") + .arg(client_.data()->getHomeServer().toString(), media_params); - client_.data()->downloadImage(event.eventId(), url_); + client_.data()->downloadImage(event.eventId(), url_); - connect(client_.data(), - SIGNAL(imageDownloaded(const QString &, const QPixmap &)), - this, - SLOT(imageDownloaded(const QString &, const QPixmap &))); + connect(client_.data(), + SIGNAL(imageDownloaded(const QString &, const QPixmap &)), + this, + SLOT(imageDownloaded(const QString &, const QPixmap &))); +} + +ImageItem::ImageItem(QSharedPointer client, + const QString &url, + const QString &filename, + QWidget *parent) + : QWidget(parent) + , url_{ url } + , text_{ QFileInfo(filename).fileName() } + , client_{ client } +{ + setMouseTracking(true); + setCursor(Qt::PointingHandCursor); + setAttribute(Qt::WA_Hover, true); + + QList url_parts = url_.toString().split("mxc://"); + + if (url_parts.size() != 2) { + qDebug() << "Invalid format for image" << url_.toString(); + return; + } + + QString media_params = url_parts[1]; + url_ = QString("%1/_matrix/media/r0/download/%2") + .arg(client_.data()->getHomeServer().toString(), media_params); + + setImage(QPixmap(filename)); } void ImageItem::imageDownloaded(const QString &event_id, const QPixmap &img) { - if (event_id != event_.eventId()) - return; + if (event_id != event_.eventId()) + return; - setImage(img); + setImage(img); } void ImageItem::openUrl() { - if (url_.toString().isEmpty()) - return; + if (url_.toString().isEmpty()) + return; - if (!QDesktopServices::openUrl(url_)) - qWarning() << "Could not open url" << url_.toString(); + if (!QDesktopServices::openUrl(url_)) + qWarning() << "Could not open url" << url_.toString(); } void ImageItem::scaleImage() { - if (image_.isNull()) - return; + if (image_.isNull()) + return; - auto width_ratio = (double)max_width_ / (double)image_.width(); - auto height_ratio = (double)max_height_ / (double)image_.height(); + auto width_ratio = (double)max_width_ / (double)image_.width(); + auto height_ratio = (double)max_height_ / (double)image_.height(); - auto min_aspect_ratio = std::min(width_ratio, height_ratio); + auto min_aspect_ratio = std::min(width_ratio, height_ratio); - if (min_aspect_ratio > 1) { - width_ = image_.width(); - height_ = image_.height(); - } else { - width_ = image_.width() * min_aspect_ratio; - height_ = image_.height() * min_aspect_ratio; - } + if (min_aspect_ratio > 1) { + width_ = image_.width(); + height_ = image_.height(); + } else { + width_ = image_.width() * min_aspect_ratio; + height_ = image_.height() * min_aspect_ratio; + } - setFixedSize(width_, height_); - scaled_image_ = image_.scaled(width_, height_, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + setFixedSize(width_, height_); + scaled_image_ = + image_.scaled(width_, height_, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } QSize ImageItem::sizeHint() const { - if (image_.isNull()) - return QSize(max_width_, bottom_height_); + if (image_.isNull()) + return QSize(max_width_, bottom_height_); - return QSize(width_, height_); + return QSize(width_, height_); } void ImageItem::setImage(const QPixmap &image) { - image_ = image; - scaleImage(); - update(); + image_ = image; + scaleImage(); + update(); } void ImageItem::mousePressEvent(QMouseEvent *event) { - if (event->button() != Qt::LeftButton) - return; + if (event->button() != Qt::LeftButton) + return; - if (image_.isNull()) { - openUrl(); - return; - } + if (image_.isNull()) { + openUrl(); + return; + } - auto point = event->pos(); + auto point = event->pos(); - // Click on the text box. - if (QRect(0, height_ - bottom_height_, width_, bottom_height_).contains(point)) { - openUrl(); - } else { - auto image_dialog = new ImageOverlayDialog(image_, this); - image_dialog->show(); - } + // Click on the text box. + if (QRect(0, height_ - bottom_height_, width_, bottom_height_).contains(point)) { + openUrl(); + } else { + auto image_dialog = new ImageOverlayDialog(image_, this); + image_dialog->show(); + } } void ImageItem::resizeEvent(QResizeEvent *event) { - Q_UNUSED(event); + Q_UNUSED(event); - scaleImage(); + scaleImage(); } void ImageItem::paintEvent(QPaintEvent *event) { - Q_UNUSED(event); + Q_UNUSED(event); - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); - QFont font("Open Sans"); - font.setPixelSize(12); + QFont font("Open Sans"); + font.setPixelSize(12); - QFontMetrics metrics(font); - int fontHeight = metrics.height(); + QFontMetrics metrics(font); + int fontHeight = metrics.height(); - if (image_.isNull()) { - int height = fontHeight + 10; + if (image_.isNull()) { + int height = fontHeight + 10; - QString elidedText = metrics.elidedText(text_, Qt::ElideRight, max_width_ - 10); + QString elidedText = metrics.elidedText(text_, Qt::ElideRight, max_width_ - 10); - setFixedSize(metrics.width(elidedText), fontHeight + 10); + setFixedSize(metrics.width(elidedText), fontHeight + 10); - painter.setFont(font); - painter.setPen(QPen(QColor(66, 133, 244))); - painter.drawText(QPoint(0, height / 2 + 2), elidedText); + painter.setFont(font); + painter.setPen(QPen(QColor(66, 133, 244))); + painter.drawText(QPoint(0, height / 2 + 2), elidedText); - return; - } + return; + } - painter.fillRect(QRect(0, 0, width_, height_), scaled_image_); + painter.fillRect(QRect(0, 0, width_, height_), scaled_image_); - if (underMouse()) { - // Bottom text section - painter.fillRect(QRect(0, height_ - bottom_height_, width_, bottom_height_), - QBrush(QColor(33, 33, 33, 128))); + if (underMouse()) { + // Bottom text section + painter.fillRect(QRect(0, height_ - bottom_height_, width_, bottom_height_), + QBrush(QColor(33, 33, 33, 128))); - QString elidedText = metrics.elidedText(text_, Qt::ElideRight, width_ - 10); + QString elidedText = metrics.elidedText(text_, Qt::ElideRight, width_ - 10); - font.setWeight(500); - painter.setFont(font); - painter.setPen(QPen(QColor("white"))); - painter.drawText(QPoint(5, height_ - fontHeight / 2), elidedText); - } + font.setWeight(500); + painter.setFont(font); + painter.setPen(QPen(QColor("white"))); + painter.drawText(QPoint(5, height_ - fontHeight / 2), elidedText); + } } diff --git a/src/LoginPage.cc b/src/LoginPage.cc index c4048f98..87485878 100644 --- a/src/LoginPage.cc +++ b/src/LoginPage.cc @@ -26,274 +26,275 @@ LoginPage::LoginPage(QSharedPointer client, QWidget *parent) , inferredServerAddress_() , client_{ client } { - setStyleSheet("background-color: #f9f9f9"); + setStyleSheet("background-color: #f9f9f9"); - top_layout_ = new QVBoxLayout(); + top_layout_ = new QVBoxLayout(); - top_bar_layout_ = new QHBoxLayout(); - top_bar_layout_->setSpacing(0); - top_bar_layout_->setMargin(0); + top_bar_layout_ = new QHBoxLayout(); + top_bar_layout_->setSpacing(0); + top_bar_layout_->setMargin(0); - back_button_ = new FlatButton(this); - back_button_->setMinimumSize(QSize(30, 30)); - back_button_->setForegroundColor("#333333"); + back_button_ = new FlatButton(this); + back_button_->setMinimumSize(QSize(30, 30)); + back_button_->setForegroundColor("#333333"); - top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter); - top_bar_layout_->addStretch(1); + top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter); + top_bar_layout_->addStretch(1); - QIcon icon; - icon.addFile(":/icons/icons/left-angle.png", QSize(), QIcon::Normal, QIcon::Off); + QIcon icon; + icon.addFile(":/icons/icons/left-angle.png", QSize(), QIcon::Normal, QIcon::Off); - back_button_->setIcon(icon); - back_button_->setIconSize(QSize(24, 24)); + back_button_->setIcon(icon); + back_button_->setIconSize(QSize(24, 24)); - QIcon advanced_settings_icon; - advanced_settings_icon.addFile(":/icons/icons/cog.png", QSize(), QIcon::Normal, QIcon::Off); + QIcon advanced_settings_icon; + advanced_settings_icon.addFile(":/icons/icons/cog.png", QSize(), QIcon::Normal, QIcon::Off); - logo_ = new QLabel(this); - logo_->setPixmap(QPixmap(":/logos/nheko-128.png")); + logo_ = new QLabel(this); + logo_->setPixmap(QPixmap(":/logos/nheko-128.png")); - logo_layout_ = new QHBoxLayout(); - logo_layout_->setContentsMargins(0, 0, 0, 20); - logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter); + logo_layout_ = new QHBoxLayout(); + logo_layout_->setContentsMargins(0, 0, 0, 20); + logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter); - form_wrapper_ = new QHBoxLayout(); - form_widget_ = new QWidget(); - form_widget_->setMinimumSize(QSize(350, 200)); + form_wrapper_ = new QHBoxLayout(); + form_widget_ = new QWidget(); + form_widget_->setMinimumSize(QSize(350, 200)); - form_layout_ = new QVBoxLayout(); - form_layout_->setSpacing(20); - form_layout_->setContentsMargins(0, 0, 0, 30); - form_widget_->setLayout(form_layout_); + form_layout_ = new QVBoxLayout(); + form_layout_->setSpacing(20); + form_layout_->setContentsMargins(0, 0, 0, 30); + form_widget_->setLayout(form_layout_); - form_wrapper_->addStretch(1); - form_wrapper_->addWidget(form_widget_); - form_wrapper_->addStretch(1); + form_wrapper_->addStretch(1); + form_wrapper_->addWidget(form_widget_); + form_wrapper_->addStretch(1); - matrixid_input_ = new TextField(this); - matrixid_input_->setTextColor("#333333"); - matrixid_input_->setLabel(tr("Matrix ID")); - matrixid_input_->setInkColor("#555459"); - matrixid_input_->setBackgroundColor("#f9f9f9"); - matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org")); + matrixid_input_ = new TextField(this); + matrixid_input_->setTextColor("#333333"); + matrixid_input_->setLabel(tr("Matrix ID")); + matrixid_input_->setInkColor("#555459"); + matrixid_input_->setBackgroundColor("#f9f9f9"); + matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org")); - spinner_ = new CircularProgress(this); - spinner_->setColor("#acc7dc"); - spinner_->setSize(32); - spinner_->setMaximumWidth(spinner_->width()); - spinner_->hide(); + spinner_ = new LoadingIndicator(this); + spinner_->setColor("#acc7dc"); + spinner_->setFixedHeight(40); + spinner_->setFixedWidth(40); + spinner_->hide(); - errorIcon_ = new QLabel(this); - errorIcon_->setPixmap(QPixmap(":/icons/icons/error.png")); - errorIcon_->hide(); + errorIcon_ = new QLabel(this); + errorIcon_->setPixmap(QPixmap(":/icons/icons/error.png")); + errorIcon_->hide(); - matrixidLayout_ = new QHBoxLayout(); - matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter); + matrixidLayout_ = new QHBoxLayout(); + matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter); - password_input_ = new TextField(this); - password_input_->setTextColor("#333333"); - password_input_->setLabel(tr("Password")); - password_input_->setInkColor("#555459"); - password_input_->setBackgroundColor("#f9f9f9"); - password_input_->setEchoMode(QLineEdit::Password); + password_input_ = new TextField(this); + password_input_->setTextColor("#333333"); + password_input_->setLabel(tr("Password")); + password_input_->setInkColor("#555459"); + password_input_->setBackgroundColor("#f9f9f9"); + password_input_->setEchoMode(QLineEdit::Password); - serverInput_ = new TextField(this); - serverInput_->setTextColor("#333333"); - serverInput_->setLabel("Homeserver address"); - serverInput_->setInkColor("#555459"); - serverInput_->setBackgroundColor("#f9f9f9"); - serverInput_->setPlaceholderText("matrix.org"); - serverInput_->hide(); + serverInput_ = new TextField(this); + serverInput_->setTextColor("#333333"); + serverInput_->setLabel("Homeserver address"); + serverInput_->setInkColor("#555459"); + serverInput_->setBackgroundColor("#f9f9f9"); + serverInput_->setPlaceholderText("matrix.org"); + serverInput_->hide(); - serverLayout_ = new QHBoxLayout(); - serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter); + serverLayout_ = new QHBoxLayout(); + serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter); - form_layout_->addLayout(matrixidLayout_); - form_layout_->addWidget(password_input_, Qt::AlignHCenter, 0); - form_layout_->addLayout(serverLayout_); + form_layout_->addLayout(matrixidLayout_); + form_layout_->addWidget(password_input_, Qt::AlignHCenter, 0); + form_layout_->addLayout(serverLayout_); - button_layout_ = new QHBoxLayout(); - button_layout_->setSpacing(0); - button_layout_->setContentsMargins(0, 0, 0, 30); + button_layout_ = new QHBoxLayout(); + button_layout_->setSpacing(0); + button_layout_->setContentsMargins(0, 0, 0, 30); - login_button_ = new RaisedButton(tr("LOGIN"), this); - login_button_->setBackgroundColor(QColor("#333333")); - login_button_->setForegroundColor(QColor("white")); - login_button_->setMinimumSize(350, 65); - login_button_->setFontSize(20); - login_button_->setCornerRadius(3); + login_button_ = new RaisedButton(tr("LOGIN"), this); + login_button_->setBackgroundColor(QColor("#333333")); + login_button_->setForegroundColor(QColor("white")); + login_button_->setMinimumSize(350, 65); + login_button_->setFontSize(20); + login_button_->setCornerRadius(3); - button_layout_->addStretch(1); - button_layout_->addWidget(login_button_); - button_layout_->addStretch(1); + button_layout_->addStretch(1); + button_layout_->addWidget(login_button_); + button_layout_->addStretch(1); - QFont font; - font.setPixelSize(conf::fontSize); + QFont font; + font.setPixelSize(conf::fontSize); - error_label_ = new QLabel(this); - error_label_->setFont(font); - error_label_->setStyleSheet("color: #E22826"); + error_label_ = new QLabel(this); + error_label_->setFont(font); + error_label_->setStyleSheet("color: #E22826"); - top_layout_->addLayout(top_bar_layout_); - top_layout_->addStretch(1); - top_layout_->addLayout(logo_layout_); - top_layout_->addLayout(form_wrapper_); - top_layout_->addStretch(1); - top_layout_->addLayout(button_layout_); - top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter); - top_layout_->addStretch(1); + top_layout_->addLayout(top_bar_layout_); + top_layout_->addStretch(1); + top_layout_->addLayout(logo_layout_); + top_layout_->addLayout(form_wrapper_); + top_layout_->addStretch(1); + top_layout_->addLayout(button_layout_); + top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter); + top_layout_->addStretch(1); - setLayout(top_layout_); + setLayout(top_layout_); - connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked())); - connect(login_button_, SIGNAL(clicked()), this, SLOT(onLoginButtonClicked())); - connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); - connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); - connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click())); - connect(client_.data(), SIGNAL(loginError(QString)), this, SLOT(loginError(QString))); - connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered())); - connect(client_.data(), SIGNAL(versionError(QString)), this, SLOT(versionError(QString))); - connect(client_.data(), SIGNAL(versionSuccess()), this, SLOT(versionSuccess())); - connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered())); + connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked())); + connect(login_button_, SIGNAL(clicked()), this, SLOT(onLoginButtonClicked())); + connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); + connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); + connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click())); + connect(client_.data(), SIGNAL(loginError(QString)), this, SLOT(loginError(QString))); + connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered())); + connect(client_.data(), SIGNAL(versionError(QString)), this, SLOT(versionError(QString))); + connect(client_.data(), SIGNAL(versionSuccess()), this, SLOT(versionSuccess())); + connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered())); } void LoginPage::loginError(QString error) { - error_label_->setText(error); + error_label_->setText(error); } bool LoginPage::isMatrixIdValid() { - int pos = 0; - auto matrix_id = matrixid_input_->text(); + int pos = 0; + auto matrix_id = matrixid_input_->text(); - return InputValidator::Id.validate(matrix_id, pos) == QValidator::Acceptable; + return InputValidator::Id.validate(matrix_id, pos) == QValidator::Acceptable; } void LoginPage::onMatrixIdEntered() { - error_label_->setText(""); + error_label_->setText(""); - if (!isMatrixIdValid()) { - loginError("You have entered an invalid Matrix ID e.g @joe:matrix.org"); - return; - } else if (password_input_->text().isEmpty()) { - loginError(tr("Empty password")); - } + if (!isMatrixIdValid()) { + loginError("You have entered an invalid Matrix ID e.g @joe:matrix.org"); + return; + } else if (password_input_->text().isEmpty()) { + loginError(tr("Empty password")); + } - QString homeServer = matrixid_input_->text().split(":").at(1); - if (homeServer != inferredServerAddress_) { - serverInput_->hide(); - serverLayout_->removeWidget(errorIcon_); - errorIcon_->hide(); - if (serverInput_->isVisible()) { - matrixidLayout_->removeWidget(spinner_); - serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); - spinner_->show(); - } else { - serverLayout_->removeWidget(spinner_); - matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); - spinner_->show(); - } + QString homeServer = matrixid_input_->text().split(":").at(1); + if (homeServer != inferredServerAddress_) { + serverInput_->hide(); + serverLayout_->removeWidget(errorIcon_); + errorIcon_->hide(); + if (serverInput_->isVisible()) { + matrixidLayout_->removeWidget(spinner_); + serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); + spinner_->start(); + } else { + serverLayout_->removeWidget(spinner_); + matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); + spinner_->start(); + } - inferredServerAddress_ = homeServer; - serverInput_->setText(homeServer); - client_->setServer(homeServer); - client_->versions(); - } + inferredServerAddress_ = homeServer; + serverInput_->setText(homeServer); + client_->setServer(homeServer); + client_->versions(); + } } void LoginPage::onServerAddressEntered() { - error_label_->setText(""); - client_->setServer(serverInput_->text()); - client_->versions(); + error_label_->setText(""); + client_->setServer(serverInput_->text()); + client_->versions(); - serverLayout_->removeWidget(errorIcon_); - errorIcon_->hide(); - serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); - spinner_->show(); + serverLayout_->removeWidget(errorIcon_); + errorIcon_->hide(); + serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); + spinner_->start(); } void LoginPage::versionError(QString error) { - // Matrix homeservers are often kept on a subdomain called 'matrix' - // so let's try that next, unless the address was set explicitly or the domain part of the username already - // points to this subdomain - QUrl currentServer = client_->getHomeServer(); - QString mxidAddress = matrixid_input_->text().split(":").at(1); - if (currentServer.host() == inferredServerAddress_ && !currentServer.host().startsWith("matrix")) { - error_label_->setText(""); - currentServer.setHost(QString("matrix.") + currentServer.host()); - serverInput_->setText(currentServer.host()); - client_->setServer(currentServer.host()); - client_->versions(); - return; - } + // Matrix homeservers are often kept on a subdomain called 'matrix' + // so let's try that next, unless the address was set explicitly or the domain part of the + // username already points to this subdomain + QUrl currentServer = client_->getHomeServer(); + QString mxidAddress = matrixid_input_->text().split(":").at(1); + if (currentServer.host() == inferredServerAddress_ && + !currentServer.host().startsWith("matrix")) { + error_label_->setText(""); + currentServer.setHost(QString("matrix.") + currentServer.host()); + serverInput_->setText(currentServer.host()); + client_->setServer(currentServer.host()); + client_->versions(); + return; + } - error_label_->setText(error); - serverInput_->show(); + error_label_->setText(error); + serverInput_->show(); - spinner_->hide(); - serverLayout_->removeWidget(spinner_); - serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight); - errorIcon_->show(); - matrixidLayout_->removeWidget(spinner_); + spinner_->stop(); + serverLayout_->removeWidget(spinner_); + serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight); + errorIcon_->show(); + matrixidLayout_->removeWidget(spinner_); } void LoginPage::versionSuccess() { - serverLayout_->removeWidget(spinner_); - matrixidLayout_->removeWidget(spinner_); - spinner_->hide(); + serverLayout_->removeWidget(spinner_); + matrixidLayout_->removeWidget(spinner_); + spinner_->stop(); - if (serverInput_->isVisible()) - serverInput_->hide(); + if (serverInput_->isVisible()) + serverInput_->hide(); } void LoginPage::onLoginButtonClicked() { - error_label_->setText(""); + error_label_->setText(""); - if (!isMatrixIdValid()) { - loginError("You have entered an invalid Matrix ID e.g @joe:matrix.org"); - } else if (password_input_->text().isEmpty()) { - loginError("Empty password"); - } else { - QString user = matrixid_input_->text().split(":").at(0).split("@").at(1); - QString password = password_input_->text(); - client_->setServer(serverInput_->text()); - client_->login(user, password); - } + if (!isMatrixIdValid()) { + loginError("You have entered an invalid Matrix ID e.g @joe:matrix.org"); + } else if (password_input_->text().isEmpty()) { + loginError("Empty password"); + } else { + QString user = matrixid_input_->text().split(":").at(0).split("@").at(1); + QString password = password_input_->text(); + client_->setServer(serverInput_->text()); + client_->login(user, password); + } } void LoginPage::reset() { - matrixid_input_->clear(); - password_input_->clear(); - serverInput_->clear(); + matrixid_input_->clear(); + password_input_->clear(); + serverInput_->clear(); - spinner_->hide(); - errorIcon_->hide(); - serverLayout_->removeWidget(spinner_); - serverLayout_->removeWidget(errorIcon_); - matrixidLayout_->removeWidget(spinner_); + spinner_->stop(); + errorIcon_->hide(); + serverLayout_->removeWidget(spinner_); + serverLayout_->removeWidget(errorIcon_); + matrixidLayout_->removeWidget(spinner_); - inferredServerAddress_.clear(); + inferredServerAddress_.clear(); } void LoginPage::onBackButtonClicked() { - emit backButtonClicked(); + emit backButtonClicked(); } LoginPage::~LoginPage() diff --git a/src/MainWindow.cc b/src/MainWindow.cc index 1567e8ba..c76657d7 100644 --- a/src/MainWindow.cc +++ b/src/MainWindow.cc @@ -15,8 +15,8 @@ * along with this program. If not, see . */ -#include "MainWindow.h" #include "Config.h" +#include "MainWindow.h" #include #include @@ -30,217 +30,225 @@ MainWindow::MainWindow(QWidget *parent) , progress_modal_{ nullptr } , spinner_{ nullptr } { - QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - setSizePolicy(sizePolicy); - setWindowTitle("nheko"); - setObjectName("MainWindow"); - setStyleSheet("QWidget#MainWindow {background-color: #f9f9f9}"); + QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + setSizePolicy(sizePolicy); + setWindowTitle("nheko"); + setObjectName("MainWindow"); + setStyleSheet("QWidget#MainWindow {background-color: #f9f9f9}"); - restoreWindowSize(); - setMinimumSize(QSize(conf::window::minWidth, conf::window::minHeight)); + restoreWindowSize(); + setMinimumSize(QSize(conf::window::minWidth, conf::window::minHeight)); - QFont font("Open Sans"); - font.setPixelSize(conf::fontSize); - font.setStyleStrategy(QFont::PreferAntialias); - setFont(font); + QFont font("Open Sans"); + font.setPixelSize(conf::fontSize); + font.setStyleStrategy(QFont::PreferAntialias); + setFont(font); - client_ = QSharedPointer(new MatrixClient("matrix.org")); - trayIcon_ = new TrayIcon(":/logos/nheko-32.png", this); + client_ = QSharedPointer(new MatrixClient("matrix.org")); + trayIcon_ = new TrayIcon(":/logos/nheko-32.png", this); - welcome_page_ = new WelcomePage(this); - login_page_ = new LoginPage(client_, this); - register_page_ = new RegisterPage(client_, this); - chat_page_ = new ChatPage(client_, this); + welcome_page_ = new WelcomePage(this); + login_page_ = new LoginPage(client_, this); + register_page_ = new RegisterPage(client_, this); + chat_page_ = new ChatPage(client_, this); - // Initialize sliding widget manager. - sliding_stack_ = new SlidingStackWidget(this); - sliding_stack_->addWidget(welcome_page_); - sliding_stack_->addWidget(login_page_); - sliding_stack_->addWidget(register_page_); - sliding_stack_->addWidget(chat_page_); + // Initialize sliding widget manager. + sliding_stack_ = new SlidingStackWidget(this); + sliding_stack_->addWidget(welcome_page_); + sliding_stack_->addWidget(login_page_); + sliding_stack_->addWidget(register_page_); + sliding_stack_->addWidget(chat_page_); - setCentralWidget(sliding_stack_); + setCentralWidget(sliding_stack_); - connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage())); - connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage())); + connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage())); + connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage())); - connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); - connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); + connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); + connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); - connect(chat_page_, SIGNAL(close()), this, SLOT(showWelcomePage())); - connect(chat_page_, SIGNAL(changeWindowTitle(QString)), this, SLOT(setWindowTitle(QString))); - connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int))); + connect(chat_page_, SIGNAL(close()), this, SLOT(showWelcomePage())); + connect( + chat_page_, SIGNAL(changeWindowTitle(QString)), this, SLOT(setWindowTitle(QString))); + connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int))); - connect(trayIcon_, - SIGNAL(activated(QSystemTrayIcon::ActivationReason)), - this, - SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); + connect(trayIcon_, + SIGNAL(activated(QSystemTrayIcon::ActivationReason)), + this, + SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); - connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar())); + connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar())); - connect(client_.data(), - SIGNAL(loginSuccess(QString, QString, QString)), - this, - SLOT(showChatPage(QString, QString, QString))); + connect(client_.data(), + SIGNAL(loginSuccess(QString, QString, QString)), + this, + SLOT(showChatPage(QString, QString, QString))); - QSettings settings; + QSettings settings; - if (hasActiveUser()) { - QString token = settings.value("auth/access_token").toString(); - QString home_server = settings.value("auth/home_server").toString(); - QString user_id = settings.value("auth/user_id").toString(); + if (hasActiveUser()) { + QString token = settings.value("auth/access_token").toString(); + QString home_server = settings.value("auth/home_server").toString(); + QString user_id = settings.value("auth/user_id").toString(); - showChatPage(user_id, home_server, token); - } + showChatPage(user_id, home_server, token); + } } void MainWindow::restoreWindowSize() { - QSettings settings; - int savedWidth = settings.value("window/width").toInt(); - int savedheight = settings.value("window/height").toInt(); + QSettings settings; + int savedWidth = settings.value("window/width").toInt(); + int savedheight = settings.value("window/height").toInt(); - if (savedWidth == 0 || savedheight == 0) - resize(conf::window::width, conf::window::height); - else - resize(savedWidth, savedheight); + if (savedWidth == 0 || savedheight == 0) + resize(conf::window::width, conf::window::height); + else + resize(savedWidth, savedheight); } void MainWindow::saveCurrentWindowSize() { - QSettings settings; - QSize current = size(); + QSettings settings; + QSize current = size(); - settings.setValue("window/width", current.width()); - settings.setValue("window/height", current.height()); + settings.setValue("window/width", current.width()); + settings.setValue("window/height", current.height()); } void MainWindow::removeOverlayProgressBar() { - QTimer *timer = new QTimer(this); - timer->setSingleShot(true); + QTimer *timer = new QTimer(this); + timer->setSingleShot(true); - connect(timer, &QTimer::timeout, [=]() { - timer->deleteLater(); + connect(timer, &QTimer::timeout, [=]() { + timer->deleteLater(); - if (progress_modal_ != nullptr) { - progress_modal_->deleteLater(); - progress_modal_->fadeOut(); - } + if (progress_modal_ != nullptr) { + progress_modal_->deleteLater(); + progress_modal_->fadeOut(); + } - if (spinner_ != nullptr) - spinner_->deleteLater(); + if (spinner_ != nullptr) + spinner_->deleteLater(); - progress_modal_ = nullptr; - spinner_ = nullptr; - }); + spinner_->stop(); - timer->start(500); + progress_modal_ = nullptr; + spinner_ = nullptr; + }); + + timer->start(500); } void MainWindow::showChatPage(QString userid, QString homeserver, QString token) { - QSettings settings; - settings.setValue("auth/access_token", token); - settings.setValue("auth/home_server", homeserver); - settings.setValue("auth/user_id", userid); + QSettings settings; + settings.setValue("auth/access_token", token); + settings.setValue("auth/home_server", homeserver); + settings.setValue("auth/user_id", userid); - int index = sliding_stack_->getWidgetIndex(chat_page_); - int modalOpacityDuration = 300; + int index = sliding_stack_->getWidgetIndex(chat_page_); + int modalOpacityDuration = 300; - // If we go directly from the welcome page don't show an animation. - if (sliding_stack_->currentIndex() == 0) { - sliding_stack_->setCurrentIndex(index); - modalOpacityDuration = 0; - } else { - sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT); - } + // If we go directly from the welcome page don't show an animation. + if (sliding_stack_->currentIndex() == 0) { + sliding_stack_->setCurrentIndex(index); + modalOpacityDuration = 0; + } else { + sliding_stack_->slideInIndex(index, + SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT); + } - if (spinner_ == nullptr) { - spinner_ = new CircularProgress(this); - spinner_->setColor("#acc7dc"); - spinner_->setSize(100); - } + if (spinner_ == nullptr) { + spinner_ = new LoadingIndicator(this); + spinner_->setColor("#acc7dc"); + spinner_->setFixedHeight(120); + spinner_->setFixedWidth(120); + spinner_->start(); + } - if (progress_modal_ == nullptr) { - progress_modal_ = new OverlayModal(this, spinner_); - progress_modal_->fadeIn(); - progress_modal_->setDuration(modalOpacityDuration); - } + if (progress_modal_ == nullptr) { + progress_modal_ = new OverlayModal(this, spinner_); + progress_modal_->fadeIn(); + progress_modal_->setDuration(modalOpacityDuration); + } - login_page_->reset(); - chat_page_->bootstrap(userid, homeserver, token); + login_page_->reset(); + chat_page_->bootstrap(userid, homeserver, token); - instance_ = this; + instance_ = this; } void MainWindow::showWelcomePage() { - int index = sliding_stack_->getWidgetIndex(welcome_page_); + int index = sliding_stack_->getWidgetIndex(welcome_page_); - if (sliding_stack_->currentIndex() == sliding_stack_->getWidgetIndex(login_page_)) - sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::RIGHT_TO_LEFT); - else - sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT); + if (sliding_stack_->currentIndex() == sliding_stack_->getWidgetIndex(login_page_)) + sliding_stack_->slideInIndex(index, + SlidingStackWidget::AnimationDirection::RIGHT_TO_LEFT); + else + sliding_stack_->slideInIndex(index, + SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT); } void MainWindow::showLoginPage() { - int index = sliding_stack_->getWidgetIndex(login_page_); - sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT); + int index = sliding_stack_->getWidgetIndex(login_page_); + sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT); } void MainWindow::showRegisterPage() { - int index = sliding_stack_->getWidgetIndex(register_page_); - sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::RIGHT_TO_LEFT); + int index = sliding_stack_->getWidgetIndex(register_page_); + sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::RIGHT_TO_LEFT); } void MainWindow::closeEvent(QCloseEvent *event) { - if (isVisible()) { - event->ignore(); - hide(); - } + if (isVisible()) { + event->ignore(); + hide(); + } } void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) { - switch (reason) { - case QSystemTrayIcon::Trigger: - if (!isVisible()) { - show(); - } else { - hide(); - } - break; - default: - break; - } + switch (reason) { + case QSystemTrayIcon::Trigger: + if (!isVisible()) { + show(); + } else { + hide(); + } + break; + default: + break; + } } bool MainWindow::hasActiveUser() { - QSettings settings; + QSettings settings; - return settings.contains("auth/access_token") && settings.contains("auth/home_server") && - settings.contains("auth/user_id"); + return settings.contains("auth/access_token") && settings.contains("auth/home_server") && + settings.contains("auth/user_id"); } MainWindow * MainWindow::instance() { - return instance_; + return instance_; } MainWindow::~MainWindow() diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc index e053642d..2e4f7c47 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc @@ -16,6 +16,8 @@ */ #include +#include +#include #include #include #include @@ -34,11 +36,10 @@ MatrixClient::MatrixClient(QString server, QObject *parent) : QNetworkAccessManager(parent) + , clientApiUrl_{ "/_matrix/client/r0" } + , mediaApiUrl_{ "/_matrix/media/r0" } + , server_{ "https://" + server } { - server_ = "https://" + server; - api_url_ = "/_matrix/client/r0"; - token_ = ""; - QSettings settings; txn_id_ = settings.value("client/transaction_id", 1).toInt(); @@ -236,6 +237,42 @@ MatrixClient::onInitialSyncResponse(QNetworkReply *reply) emit initialSyncCompleted(response); } +void +MatrixClient::onImageUploadResponse(QNetworkReply *reply) +{ + reply->deleteLater(); + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (status == 0 || status >= 400) { + emit syncFailed(reply->errorString()); + return; + } + + auto data = reply->readAll(); + + if (data.isEmpty()) + return; + + auto json = QJsonDocument::fromJson(data); + + if (!json.isObject()) { + qDebug() << "Media upload: Response is not a json object."; + return; + } + + QJsonObject object = json.object(); + if (!object.contains("content_uri")) { + qDebug() << "Media upload: Missing content_uri key"; + qDebug() << object; + return; + } + + emit imageUploaded(reply->property("room_id").toString(), + reply->property("filename").toString(), + object.value("content_uri").toString()); +} + void MatrixClient::onSyncResponse(QNetworkReply *reply) { @@ -450,6 +487,9 @@ MatrixClient::onResponse(QNetworkReply *reply) case Endpoint::InitialSync: onInitialSyncResponse(reply); break; + case Endpoint::ImageUpload: + onImageUploadResponse(reply); + break; case Endpoint::Sync: onSyncResponse(reply); break; @@ -477,7 +517,7 @@ void MatrixClient::login(const QString &username, const QString &password) noexcept { QUrl endpoint(server_); - endpoint.setPath(api_url_ + "/login"); + endpoint.setPath(clientApiUrl_ + "/login"); QNetworkRequest request(endpoint); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); @@ -495,7 +535,7 @@ MatrixClient::logout() noexcept query.addQueryItem("access_token", token_); QUrl endpoint(server_); - endpoint.setPath(api_url_ + "/logout"); + endpoint.setPath(clientApiUrl_ + "/logout"); endpoint.setQuery(query); QNetworkRequest request(endpoint); @@ -515,7 +555,7 @@ MatrixClient::registerUser(const QString &user, const QString &pass, const QStri query.addQueryItem("kind", "user"); QUrl endpoint(server_); - endpoint.setPath(api_url_ + "/register"); + endpoint.setPath(clientApiUrl_ + "/register"); endpoint.setQuery(query); QNetworkRequest request(QString(endpoint.toEncoded())); @@ -549,7 +589,7 @@ MatrixClient::sync() noexcept query.addQueryItem("since", next_batch_); QUrl endpoint(server_); - endpoint.setPath(api_url_ + "/sync"); + endpoint.setPath(clientApiUrl_ + "/sync"); endpoint.setQuery(query); QNetworkRequest request(QString(endpoint.toEncoded())); @@ -561,32 +601,35 @@ MatrixClient::sync() noexcept void MatrixClient::sendRoomMessage(matrix::events::MessageEventType ty, const QString &roomid, - const QString &msg) noexcept + const QString &msg, + const QString &url) noexcept { QUrlQuery query; query.addQueryItem("access_token", token_); QUrl endpoint(server_); - endpoint.setPath(api_url_ + + endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/send/m.room.message/%2").arg(roomid).arg(txn_id_)); endpoint.setQuery(query); QString msgType(""); + QJsonObject body; switch (ty) { case matrix::events::MessageEventType::Text: - msgType = "m.text"; + body = { { "msgtype", "m.text" }, { "body", msg } }; break; case matrix::events::MessageEventType::Emote: - msgType = "m.emote"; + body = { { "msgtype", "m.emote" }, { "body", msg } }; + break; + case matrix::events::MessageEventType::Image: + body = { { "msgtype", "m.image" }, { "body", msg }, { "url", url } }; break; default: - msgType = "m.text"; - break; + qDebug() << "SendRoomMessage: Unknown message type for" << msg; + return; } - QJsonObject body{ { "msgtype", msgType }, { "body", msg } }; - QNetworkRequest request(QString(endpoint.toEncoded())); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); @@ -617,7 +660,7 @@ MatrixClient::initialSync() noexcept query.addQueryItem("access_token", token_); QUrl endpoint(server_); - endpoint.setPath(api_url_ + "/sync"); + endpoint.setPath(clientApiUrl_ + "/sync"); endpoint.setQuery(query); QNetworkRequest request(QString(endpoint.toEncoded())); @@ -650,7 +693,7 @@ MatrixClient::getOwnProfile() noexcept query.addQueryItem("access_token", token_); QUrl endpoint(server_); - endpoint.setPath(api_url_ + "/profile/" + userid); + endpoint.setPath(clientApiUrl_ + "/profile/" + userid); endpoint.setQuery(query); QNetworkRequest request(QString(endpoint.toEncoded())); @@ -762,7 +805,7 @@ MatrixClient::messages(const QString &room_id, const QString &from_token, int li query.addQueryItem("limit", QString::number(limit)); QUrl endpoint(server_); - endpoint.setPath(api_url_ + QString("/rooms/%1/messages").arg(room_id)); + endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/messages").arg(room_id)); endpoint.setQuery(query); QNetworkRequest request(QString(endpoint.toEncoded())); @@ -771,3 +814,31 @@ MatrixClient::messages(const QString &room_id, const QString &from_token, int li reply->setProperty("endpoint", static_cast(Endpoint::Messages)); reply->setProperty("room_id", room_id); } + +void +MatrixClient::uploadImage(const QString &roomid, const QString &filename) +{ + QUrlQuery query; + query.addQueryItem("access_token", token_); + + QUrl endpoint(server_); + endpoint.setPath(mediaApiUrl_ + "/upload"); + endpoint.setQuery(query); + + QFile file(filename); + if (!file.open(QIODevice::ReadWrite)) { + qDebug() << "Error while reading" << filename; + return; + } + + auto imgFormat = QString(QImageReader::imageFormat(filename)); + + QNetworkRequest request(QString(endpoint.toEncoded())); + request.setHeader(QNetworkRequest::ContentLengthHeader, file.size()); + request.setHeader(QNetworkRequest::ContentTypeHeader, QString("image/%1").arg(imgFormat)); + + QNetworkReply *reply = post(request, file.readAll()); + reply->setProperty("endpoint", static_cast(Endpoint::ImageUpload)); + reply->setProperty("room_id", roomid); + reply->setProperty("filename", filename); +} diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc index bd74186e..ce208feb 100644 --- a/src/TextInputWidget.cc +++ b/src/TextInputWidget.cc @@ -17,6 +17,8 @@ #include #include +#include +#include #include #include @@ -47,17 +49,23 @@ TextInputWidget::TextInputWidget(QWidget *parent) setCursor(Qt::ArrowCursor); setStyleSheet("background-color: #f8fbfe; height: 45px;"); - top_layout_ = new QHBoxLayout(); - top_layout_->setSpacing(0); - top_layout_->setMargin(0); - - send_file_button_ = new FlatButton(this); + topLayout_ = new QHBoxLayout(); + topLayout_->setSpacing(2); + topLayout_->setMargin(4); QIcon send_file_icon; send_file_icon.addFile(":/icons/icons/clip-dark.png", QSize(), QIcon::Normal, QIcon::Off); - send_file_button_->setForegroundColor(QColor("#acc7dc")); - send_file_button_->setIcon(send_file_icon); - send_file_button_->setIconSize(QSize(24, 24)); + + sendFileBtn_ = new FlatButton(this); + sendFileBtn_->setForegroundColor(QColor("#acc7dc")); + sendFileBtn_->setIcon(send_file_icon); + sendFileBtn_->setIconSize(QSize(24, 24)); + + spinner_ = new LoadingIndicator(this); + spinner_->setColor("#acc7dc"); + spinner_->setFixedHeight(40); + spinner_->setFixedWidth(40); + spinner_->hide(); QFont font; font.setPixelSize(conf::fontSize); @@ -68,33 +76,34 @@ TextInputWidget::TextInputWidget(QWidget *parent) input_->setPlaceholderText(tr("Write a message...")); input_->setStyleSheet("color: #333333; border-radius: 0; padding-top: 10px;"); - send_message_button_ = new FlatButton(this); - send_message_button_->setForegroundColor(QColor("#acc7dc")); + sendMessageBtn_ = new FlatButton(this); + sendMessageBtn_->setForegroundColor(QColor("#acc7dc")); QIcon send_message_icon; send_message_icon.addFile( ":/icons/icons/share-dark.png", QSize(), QIcon::Normal, QIcon::Off); - send_message_button_->setIcon(send_message_icon); - send_message_button_->setIconSize(QSize(24, 24)); + sendMessageBtn_->setIcon(send_message_icon); + sendMessageBtn_->setIconSize(QSize(24, 24)); - emoji_button_ = new EmojiPickButton(this); - emoji_button_->setForegroundColor(QColor("#acc7dc")); + emojiBtn_ = new EmojiPickButton(this); + emojiBtn_->setForegroundColor(QColor("#acc7dc")); QIcon emoji_icon; emoji_icon.addFile(":/icons/icons/smile.png", QSize(), QIcon::Normal, QIcon::Off); - emoji_button_->setIcon(emoji_icon); - emoji_button_->setIconSize(QSize(24, 24)); + emojiBtn_->setIcon(emoji_icon); + emojiBtn_->setIconSize(QSize(24, 24)); - top_layout_->addWidget(send_file_button_); - top_layout_->addWidget(input_); - top_layout_->addWidget(emoji_button_); - top_layout_->addWidget(send_message_button_); + topLayout_->addWidget(sendFileBtn_); + topLayout_->addWidget(input_); + topLayout_->addWidget(emojiBtn_); + topLayout_->addWidget(sendMessageBtn_); - setLayout(top_layout_); + setLayout(topLayout_); - connect(send_message_button_, SIGNAL(clicked()), this, SLOT(onSendButtonClicked())); - connect(input_, SIGNAL(enterPressed()), send_message_button_, SIGNAL(clicked())); - connect(emoji_button_, + connect(sendMessageBtn_, SIGNAL(clicked()), this, SLOT(onSendButtonClicked())); + connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection())); + connect(input_, SIGNAL(enterPressed()), sendMessageBtn_, SIGNAL(clicked())); + connect(emojiBtn_, SIGNAL(emojiSelected(const QString &)), this, SLOT(addSelectedEmoji(const QString &))); @@ -155,6 +164,55 @@ TextInputWidget::parseEmoteCommand(const QString &cmd) return QString(""); } +void +TextInputWidget::openFileSelection() +{ + QStringList supportedFiles; + supportedFiles << "jpeg" + << "gif" + << "png" + << "bmp" + << "tiff" + << "webp"; + + auto fileName = QFileDialog::getOpenFileName( + this, + tr("Select an image"), + "", + tr("Image Files (*.bmp *.gif *.jpg *.jpeg *.png *.tiff *.webp)")); + + if (fileName.isEmpty()) + return; + + auto imageFormat = QString(QImageReader::imageFormat(fileName)); + if (!supportedFiles.contains(imageFormat)) { + qDebug() << "Unsupported image format for" << fileName; + return; + } + + emit uploadImage(fileName); + showUploadSpinner(); +} + +void +TextInputWidget::showUploadSpinner() +{ + topLayout_->removeWidget(sendFileBtn_); + sendFileBtn_->hide(); + + topLayout_->insertWidget(0, spinner_); + spinner_->start(); +} + +void +TextInputWidget::hideUploadSpinner() +{ + topLayout_->removeWidget(spinner_); + topLayout_->insertWidget(0, sendFileBtn_); + sendFileBtn_->show(); + spinner_->stop(); +} + TextInputWidget::~TextInputWidget() { } diff --git a/src/TimelineItem.cc b/src/TimelineItem.cc index 9d24a96c..92351d63 100644 --- a/src/TimelineItem.cc +++ b/src/TimelineItem.cc @@ -107,6 +107,39 @@ TimelineItem::TimelineItem(events::MessageEventType ty, mainLayout_->addWidget(body_); } +TimelineItem::TimelineItem(ImageItem *image, + const QString &userid, + bool withSender, + QWidget *parent) + : QWidget{ parent } +{ + init(); + + auto displayName = TimelineViewManager::displayName(userid); + auto timestamp = QDateTime::currentDateTime(); + + descriptionMsg_ = { "You", userid, " sent an image", descriptiveTime(timestamp) }; + + generateTimestamp(timestamp); + + auto imageLayout = new QHBoxLayout(); + imageLayout->setMargin(0); + imageLayout->addWidget(image); + imageLayout->addStretch(1); + + if (withSender) { + generateBody(displayName, ""); + setupAvatarLayout(displayName); + mainLayout_->addLayout(headerLayout_); + + AvatarProvider::resolve(userid, this); + } else { + setupSimpleLayout(); + } + + mainLayout_->addLayout(imageLayout); +} + /* * Used to display images. The avatar and the username are displayed. */ diff --git a/src/TimelineView.cc b/src/TimelineView.cc index 518676ac..600ccb94 100644 --- a/src/TimelineView.cc +++ b/src/TimelineView.cc @@ -223,8 +223,9 @@ TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection dire eventIds_[text.eventId()] = true; - if (isPendingMessage(text, local_user_)) { - removePendingMessage(text); + if (isPendingMessage( + text.eventId(), text.content().body(), text.sender(), local_user_)) { + removePendingMessage(text.eventId(), text.content().body()); return nullptr; } @@ -245,7 +246,6 @@ TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection dire if (isDuplicate(notice.eventId())) return nullptr; - ; eventIds_[notice.eventId()] = true; @@ -269,6 +269,12 @@ TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection dire eventIds_[img.eventId()] = true; + if (isPendingMessage( + img.eventId(), img.msgContent().url(), img.sender(), local_user_)) { + removePendingMessage(img.eventId(), img.msgContent().url()); + return nullptr; + } + auto with_sender = isSenderRendered(img.sender(), direction); updateLastSender(img.sender(), direction); @@ -289,8 +295,11 @@ TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection dire eventIds_[emote.eventId()] = true; - if (isPendingMessage(emote, local_user_)) { - removePendingMessage(emote); + if (isPendingMessage(emote.eventId(), + emote.content().body(), + emote.sender(), + local_user_)) { + removePendingMessage(emote.eventId(), emote.content().body()); return nullptr; } @@ -471,6 +480,24 @@ TimelineView::addUserMessage(matrix::events::MessageEventType ty, const QString pending_msgs_.push_back(message); } +void +TimelineView::addUserMessage(const QString &url, const QString &filename, int txn_id) +{ + QSettings settings; + auto user_id = settings.value("auth/user_id").toString(); + auto with_sender = lastSender_ != user_id; + + auto image = new ImageItem(client_, url, filename, this); + + TimelineItem *view_item = new TimelineItem(image, user_id, with_sender, scroll_widget_); + scroll_layout_->addWidget(view_item); + + lastSender_ = user_id; + + PendingMessage message(txn_id, url, "", view_item); + pending_msgs_.push_back(message); +} + void TimelineView::notifyForLastEvent() { @@ -482,3 +509,33 @@ TimelineView::notifyForLastEvent() else qWarning() << "Cast to TimelineView failed" << room_id_; } + +bool +TimelineView::isPendingMessage(const QString &eventid, + const QString &body, + const QString &sender, + const QString &local_userid) +{ + if (sender != local_userid) + return false; + + for (const auto &msg : pending_msgs_) { + if (msg.event_id == eventid || msg.body == body) + return true; + } + + return false; +} + +void +TimelineView::removePendingMessage(const QString &eventid, const QString &body) +{ + for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); it++) { + int index = std::distance(pending_msgs_.begin(), it); + + if (it->event_id == eventid || it->body == body) { + pending_msgs_.removeAt(index); + break; + } + } +} diff --git a/src/TimelineViewManager.cc b/src/TimelineViewManager.cc index 0bb56bf9..2adbba34 100644 --- a/src/TimelineViewManager.cc +++ b/src/TimelineViewManager.cc @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -72,6 +73,23 @@ TimelineViewManager::sendEmoteMessage(const QString &msg) client_->sendRoomMessage(matrix::events::MessageEventType::Emote, room_id, msg); } +void +TimelineViewManager::sendImageMessage(const QString &roomid, + const QString &filename, + const QString &url) +{ + if (!views_.contains(roomid)) { + qDebug() << "Cannot send m.image message to a non-managed view"; + return; + } + + auto view = views_[roomid]; + + view->addUserMessage(url, filename, client_->transactionId()); + client_->sendRoomMessage( + matrix::events::MessageEventType::Image, roomid, QFileInfo(filename).fileName(), url); +} + void TimelineViewManager::clearAll() { diff --git a/src/ui/CircularProgress.cc b/src/ui/CircularProgress.cc deleted file mode 100644 index 425ece13..00000000 --- a/src/ui/CircularProgress.cc +++ /dev/null @@ -1,201 +0,0 @@ -#include -#include -#include -#include - -#include "CircularProgress.h" -#include "Theme.h" - -CircularProgress::CircularProgress(QWidget *parent) - : QProgressBar{ parent } - , progress_type_{ ui::ProgressType::IndeterminateProgress } - , width_{ 6.25 } - , size_{ 64 } - , duration_{ 3050 } -{ - delegate_ = new CircularProgressDelegate(this); - - setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - - auto group = new QParallelAnimationGroup(this); - group->setLoopCount(-1); - - auto length_animation = new QPropertyAnimation(this); - length_animation->setPropertyName("dashLength"); - length_animation->setTargetObject(delegate_); - length_animation->setEasingCurve(QEasingCurve::InOutQuad); - length_animation->setStartValue(0.1); - length_animation->setKeyValueAt(0.15, 3); - length_animation->setKeyValueAt(0.6, 20); - length_animation->setKeyValueAt(0.7, 20); - length_animation->setEndValue(20); - length_animation->setDuration(duration_); - - auto offset_animation = new QPropertyAnimation(this); - offset_animation->setPropertyName("dashOffset"); - offset_animation->setTargetObject(delegate_); - offset_animation->setEasingCurve(QEasingCurve::InOutSine); - offset_animation->setStartValue(0); - offset_animation->setKeyValueAt(0.15, 0); - offset_animation->setKeyValueAt(0.6, -7); - offset_animation->setKeyValueAt(0.7, -7); - offset_animation->setEndValue(-25); - offset_animation->setDuration(duration_); - - auto angle_animation = new QPropertyAnimation(this); - angle_animation->setPropertyName("angle"); - angle_animation->setTargetObject(delegate_); - angle_animation->setStartValue(0); - angle_animation->setEndValue(360); - angle_animation->setDuration(duration_); - - group->addAnimation(length_animation); - group->addAnimation(offset_animation); - group->addAnimation(angle_animation); - - group->start(); -} - -void -CircularProgress::setProgressType(ui::ProgressType type) -{ - progress_type_ = type; - update(); -} - -void -CircularProgress::setLineWidth(qreal width) -{ - width_ = width; - update(); - updateGeometry(); -} - -void -CircularProgress::setSize(int size) -{ - size_ = size; - update(); - updateGeometry(); -} - -ui::ProgressType -CircularProgress::progressType() const -{ - return progress_type_; -} - -qreal -CircularProgress::lineWidth() const -{ - return width_; -} - -int -CircularProgress::size() const -{ - return size_; -} - -void -CircularProgress::setColor(const QColor &color) -{ - color_ = color; -} - -QColor -CircularProgress::color() const -{ - if (!color_.isValid()) { - return QColor("red"); - } - - return color_; -} - -QSize -CircularProgress::sizeHint() const -{ - const qreal s = size_ + width_ + 8; - return QSize(s, s); -} - -void -CircularProgress::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event); - - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - - /* - * If the progress bar is disabled draw an X instead - */ - if (!isEnabled()) { - QPen pen; - pen.setCapStyle(Qt::RoundCap); - pen.setWidthF(lineWidth()); - pen.setColor("gray"); - - auto center = rect().center(); - - painter.setPen(pen); - painter.drawLine(center - QPointF(20, 20), center + QPointF(20, 20)); - painter.drawLine(center + QPointF(20, -20), center - QPointF(20, -20)); - - return; - } - - if (progress_type_ == ui::ProgressType::IndeterminateProgress) { - painter.translate(width() / 2, height() / 2); - painter.rotate(delegate_->angle()); - } - - QPen pen; - pen.setCapStyle(Qt::RoundCap); - pen.setWidthF(width_); - pen.setColor(color()); - - if (ui::ProgressType::IndeterminateProgress == progress_type_) { - QVector pattern; - pattern << delegate_->dashLength() * size_ / 50 << 30 * size_ / 50; - - pen.setDashOffset(delegate_->dashOffset() * size_ / 50); - pen.setDashPattern(pattern); - - painter.setPen(pen); - - painter.drawEllipse(QPoint(0, 0), size_ / 2, size_ / 2); - } else { - painter.setPen(pen); - - const qreal x = (width() - size_) / 2; - const qreal y = (height() - size_) / 2; - - const qreal a = 360 * (value() - minimum()) / (maximum() - minimum()); - - QPainterPath path; - path.arcMoveTo(x, y, size_, size_, 0); - path.arcTo(x, y, size_, size_, 0, a); - - painter.drawPath(path); - } -} - -CircularProgress::~CircularProgress() -{ -} - -CircularProgressDelegate::CircularProgressDelegate(CircularProgress *parent) - : QObject(parent) - , progress_(parent) - , dash_offset_(0) - , dash_length_(89) - , angle_(0) -{ - Q_ASSERT(parent); -} - -CircularProgressDelegate::~CircularProgressDelegate() -{ -} diff --git a/src/ui/LoadingIndicator.cc b/src/ui/LoadingIndicator.cc new file mode 100644 index 00000000..0fafaf23 --- /dev/null +++ b/src/ui/LoadingIndicator.cc @@ -0,0 +1,86 @@ +#include "LoadingIndicator.h" + +#include +#include +#include + +LoadingIndicator::LoadingIndicator(QWidget *parent) + : QWidget(parent) + , interval_(70) + , angle_(0) + , color_(Qt::black) +{ + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setFocusPolicy(Qt::NoFocus); + + timer_ = new QTimer(); + connect(timer_, SIGNAL(timeout()), this, SLOT(onTimeout())); +} + +LoadingIndicator::~LoadingIndicator() +{ + stop(); + + delete timer_; +} + +void +LoadingIndicator::paintEvent(QPaintEvent *e) +{ + Q_UNUSED(e) + + if (!timer_->isActive()) + return; + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + int width = qMin(this->width(), this->height()); + + int outerRadius = (width - 4) * 0.5f; + int innerRadius = outerRadius * 0.78f; + + int capsuleRadius = (outerRadius - innerRadius) / 2; + + for (int i = 0; i < 8; i++) { + QColor color = color_; + + color.setAlphaF(1.0f - (i / 8.0f)); + + painter.setPen(Qt::NoPen); + painter.setBrush(color); + + qreal radius = capsuleRadius * (1.0f - (i / 16.0f)); + + painter.save(); + + painter.translate(rect().center()); + painter.rotate(angle_ - i * 45.0f); + + QPointF center = QPointF(-capsuleRadius, -innerRadius); + painter.drawEllipse(center, radius * 2, radius * 2); + + painter.restore(); + } +} + +void +LoadingIndicator::start() +{ + timer_->start(interval_); + show(); +} + +void +LoadingIndicator::stop() +{ + timer_->stop(); + hide(); +} + +void +LoadingIndicator::onTimeout() +{ + angle_ = (angle_ + 45) % 360; + update(); +}