From 3f0116c6f0f2db69fd2022e3c0c041940f8f5a3d Mon Sep 17 00:00:00 2001 From: Chris Tarazi Date: Sat, 25 Nov 2017 13:28:51 -0800 Subject: [PATCH 1/8] Support pasting images into rooms Squashes the following commits: - Use QKeySequence::Copy instead of hardcoded ctrl-c - Add ability to embed images and text in same message - Remove unneeded explicit call to insertFromMimeData() - Fix case for X11 users when copying an image - Add preview dialog for pasted images --- CMakeLists.txt | 2 + include/TextInputWidget.h | 10 +++ include/dialogs/PreviewImageOverlay.h | 51 +++++++++++++ resources/styles/nheko-dark.qss | 7 +- resources/styles/nheko.qss | 7 +- src/TextInputWidget.cc | 68 +++++++++++++++++ src/dialogs/PreviewImageOverlay.cc | 105 ++++++++++++++++++++++++++ 7 files changed, 240 insertions(+), 10 deletions(-) create mode 100644 include/dialogs/PreviewImageOverlay.h create mode 100644 src/dialogs/PreviewImageOverlay.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index b0a5c610..c6109652 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,6 +142,7 @@ set(SRC_FILES # Dialogs src/dialogs/CreateRoom.cc src/dialogs/ImageOverlay.cc + src/dialogs/PreviewImageOverlay.cc src/dialogs/InviteUsers.cc src/dialogs/JoinRoom.cc src/dialogs/LeaveRoom.cc @@ -224,6 +225,7 @@ qt5_wrap_cpp(MOC_HEADERS # Dialogs include/dialogs/CreateRoom.h include/dialogs/ImageOverlay.h + include/dialogs/PreviewImageOverlay.h include/dialogs/InviteUsers.h include/dialogs/JoinRoom.h include/dialogs/LeaveRoom.h diff --git a/include/TextInputWidget.h b/include/TextInputWidget.h index df309e27..ea3a23fa 100644 --- a/include/TextInputWidget.h +++ b/include/TextInputWidget.h @@ -29,6 +29,10 @@ #include "emoji/PickButton.h" +namespace dialogs { +class PreviewImageOverlay; +} + class FilteredTextEdit : public QTextEdit { Q_OBJECT @@ -48,16 +52,22 @@ class FilteredTextEdit : public QTextEdit void stoppedTyping(); void message(QString); void command(QString name, QString args); + void image(QString name); protected: void keyPressEvent(QKeyEvent *event) override; + bool canInsertFromMimeData(const QMimeData *source) const override; + void insertFromMimeData(const QMimeData *source) override; private: std::deque true_history_, working_history_; size_t history_index_; QTimer *typingTimer_; + dialogs::PreviewImageOverlay *previewDialog_; + void textChanged(); + void receiveImage(const QPixmap &img, const QString &img_name); void afterCompletion(int); }; diff --git a/include/dialogs/PreviewImageOverlay.h b/include/dialogs/PreviewImageOverlay.h new file mode 100644 index 00000000..45692dc5 --- /dev/null +++ b/include/dialogs/PreviewImageOverlay.h @@ -0,0 +1,51 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * 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 . + */ + +#pragma once + +#include +#include + +class QLabel; +class QLineEdit; +class QPushButton; + +namespace dialogs { + +class PreviewImageOverlay : public QWidget +{ + Q_OBJECT +public: + PreviewImageOverlay(QPixmap image, QWidget *parent = nullptr); + PreviewImageOverlay(const QString &path, QWidget *parent = nullptr); + +signals: + void confirmImageUpload(const QPixmap &img, const QString &img_name); + +private: + void init(); + + QPixmap image_; + + QLabel *titleLabel_; + QLabel *imageLabel_; + QLineEdit *imageName_; + + QPushButton *upload_; + QPushButton *cancel_; +}; +} // dialogs diff --git a/resources/styles/nheko-dark.qss b/resources/styles/nheko-dark.qss index a78fb612..9e866992 100644 --- a/resources/styles/nheko-dark.qss +++ b/resources/styles/nheko-dark.qss @@ -85,11 +85,8 @@ dialogs--LeaveRoom, dialogs--CreateRoom, dialogs--InviteUsers, dialogs--ReadReceipts, -dialogs--JoinRoom { - background-color: #383c4a; - color: #caccd1; -} - +dialogs--JoinRoom, +dialogs--PreviewImageOverlay, QListWidget { background-color: #383c4a; color: #caccd1; diff --git a/resources/styles/nheko.qss b/resources/styles/nheko.qss index ce86e212..b8c71c86 100644 --- a/resources/styles/nheko.qss +++ b/resources/styles/nheko.qss @@ -87,11 +87,8 @@ dialogs--LeaveRoom, dialogs--CreateRoom, dialogs--InviteUsers, dialogs--ReadReceipts, -dialogs--JoinRoom { - background-color: white; - color: #333; -} - +dialogs--JoinRoom, +dialogs--PreviewImageOverlay, QListWidget { background-color: white; color: #333; diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc index dc2bebe7..141c6566 100644 --- a/src/TextInputWidget.cc +++ b/src/TextInputWidget.cc @@ -16,10 +16,13 @@ */ #include +#include +#include #include #include #include #include +#include #include #include #include @@ -27,6 +30,7 @@ #include "Config.h" #include "TextInputWidget.h" +#include "dialogs/PreviewImageOverlay.h" static constexpr size_t INPUT_HISTORY_SIZE = 127; @@ -101,6 +105,43 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event) } } +bool +FilteredTextEdit::canInsertFromMimeData(const QMimeData *source) const +{ + return (source->hasImage() || source->hasText() || + QTextEdit::canInsertFromMimeData(source)); +} + +void +FilteredTextEdit::insertFromMimeData(const QMimeData *source) +{ + qDebug() << "insertFromMimeData source->text():" << source->text(); + + if (source->hasImage()) { + qDebug() << "PreviewImageOverlay show"; + QPixmap const img = qvariant_cast(source->imageData()); + previewDialog_ = new dialogs::PreviewImageOverlay{img, this}; + connect(previewDialog_, + &dialogs::PreviewImageOverlay::confirmImageUpload, + this, + &FilteredTextEdit::receiveImage); + previewDialog_->show(); + } else if (source->hasFormat("x-special/gnome-copied-files") && + QImageReader{source->text()}.canRead()) { + // Special case for X11 users. See "Notes for X11 Users" in source. + // Source: http://doc.qt.io/qt-5/qclipboard.html + qDebug() << "PreviewImageOverlay special gnome show"; + previewDialog_ = new dialogs::PreviewImageOverlay{source->text(), this}; + connect(previewDialog_, + &dialogs::PreviewImageOverlay::confirmImageUpload, + this, + &FilteredTextEdit::receiveImage); + previewDialog_->show(); + } else { + QTextEdit::insertFromMimeData(source); + } +} + void FilteredTextEdit::stopTyping() { @@ -146,6 +187,7 @@ FilteredTextEdit::submit() history_index_ = 0; QString text = toPlainText(); + if (text.startsWith('/')) { int command_end = text.indexOf(' '); if (command_end == -1) @@ -170,6 +212,31 @@ FilteredTextEdit::textChanged() working_history_[history_index_] = toPlainText(); } +void +FilteredTextEdit::receiveImage(const QPixmap &img, const QString &img_name) +{ + qDebug() << "Received image from PreviewImageOverlay:" << img; + + // Save image into temporary path to be loaded later. + QString img_path = QDir::tempPath() + '/' + img_name; + QFile file{img_path}; + + if (!file.open(QIODevice::WriteOnly)) { + qDebug() << "Failed to create temporary image file"; + previewDialog_->close(); + return; + } + if (!img.save(file.fileName(), "PNG")) { + qDebug() << "Failed to save image data"; + previewDialog_->close(); + return; + } + + emit image(img_path); + + previewDialog_->close(); +} + TextInputWidget::TextInputWidget(QWidget *parent) : QFrame(parent) { @@ -231,6 +298,7 @@ TextInputWidget::TextInputWidget(QWidget *parent) connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection())); connect(input_, &FilteredTextEdit::message, this, &TextInputWidget::sendTextMessage); connect(input_, &FilteredTextEdit::command, this, &TextInputWidget::command); + connect(input_, &FilteredTextEdit::image, this, &TextInputWidget::uploadImage); connect(emojiBtn_, SIGNAL(emojiSelected(const QString &)), this, diff --git a/src/dialogs/PreviewImageOverlay.cc b/src/dialogs/PreviewImageOverlay.cc new file mode 100644 index 00000000..3edc55ae --- /dev/null +++ b/src/dialogs/PreviewImageOverlay.cc @@ -0,0 +1,105 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dialogs/PreviewImageOverlay.h" + +using namespace dialogs; + +PreviewImageOverlay::PreviewImageOverlay(QPixmap image, QWidget *parent) + : QWidget{parent} + , image_{image} +{ + init(); +} + +PreviewImageOverlay::PreviewImageOverlay(const QString &path, QWidget *parent) + : QWidget{parent} +{ + if (!image_.load(path)) { + qDebug() << "Failed to read image from:" << path; + close(); + return; + } + + init(); +} + +void +PreviewImageOverlay::init() +{ + auto window = QApplication::activeWindow(); + auto winsize = window->frameGeometry().size(); + auto center = window->frameGeometry().center(); + auto img_size = image_.size(); + + setAutoFillBackground(true); + setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); + setWindowModality(Qt::WindowModal); + setPalette(QApplication::palette(window)); + + titleLabel_ = new QLabel{tr("Upload image?"), this}; + imageLabel_ = new QLabel{this}; + imageName_ = new QLineEdit{tr("nheko_pasted_img.png"), this}; + upload_ = new QPushButton{tr("Upload"), this}; + cancel_ = new QPushButton{tr("Cancel"), this}; + + titleLabel_->setStyleSheet("font-weight: bold; font-size: 22px;"); + titleLabel_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + titleLabel_->setAlignment(Qt::AlignCenter); + imageLabel_->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + imageLabel_->setAlignment(Qt::AlignCenter); + imageName_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + imageName_->setAlignment(Qt::AlignCenter); + upload_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + cancel_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + // Scale image preview to the size of the current window if it is larger. + if ((img_size.height() * img_size.width()) > (winsize.height() * winsize.width())) { + imageLabel_->setPixmap(image_.scaled(winsize, Qt::KeepAspectRatio)); + } else { + imageLabel_->setPixmap(image_); + move(center.x() - (width() * 0.5), center.y() - (height() * 0.5)); + } + imageLabel_->setScaledContents(false); + + auto hlayout = new QHBoxLayout; + hlayout->addWidget(upload_); + hlayout->addWidget(cancel_); + + auto vlayout = new QVBoxLayout{this}; + vlayout->addWidget(titleLabel_); + vlayout->addWidget(imageLabel_); + vlayout->addWidget(imageName_); + vlayout->addLayout(hlayout); + + connect(upload_, &QPushButton::clicked, [=]() { + emit confirmImageUpload(image_, imageName_->text()); + }); + connect(cancel_, &QPushButton::clicked, &dialogs::PreviewImageOverlay::close); + + raise(); +} From 1f7d238b53917d7a0f55458db4241a6db24f8c5e Mon Sep 17 00:00:00 2001 From: Chris Tarazi Date: Sat, 23 Dec 2017 13:31:14 -0800 Subject: [PATCH 2/8] Use generic QIODevice for uploading media --- include/MatrixClient.h | 14 +++++----- include/TextInputWidget.h | 6 ++--- include/timeline/TimelineView.h | 14 +++++----- include/timeline/TimelineViewManager.h | 14 +++++++--- include/timeline/widgets/AudioItem.h | 2 +- include/timeline/widgets/FileItem.h | 2 +- include/timeline/widgets/ImageItem.h | 2 +- src/ChatPage.cc | 24 ++++++++--------- src/MatrixClient.cc | 37 +++++++++++++------------- src/TextInputWidget.cc | 7 ++--- src/timeline/TimelineView.cc | 6 ++--- src/timeline/TimelineViewManager.cc | 12 ++++----- src/timeline/widgets/AudioItem.cc | 6 ++--- src/timeline/widgets/FileItem.cc | 6 ++--- src/timeline/widgets/ImageItem.cc | 6 ++--- 15 files changed, 83 insertions(+), 75 deletions(-) diff --git a/include/MatrixClient.h b/include/MatrixClient.h index 2627f578..3d477b9f 100644 --- a/include/MatrixClient.h +++ b/include/MatrixClient.h @@ -52,9 +52,9 @@ class MatrixClient : public QNetworkAccessManager void downloadImage(const QString &event_id, const QUrl &url); void downloadFile(const QString &event_id, const QUrl &url); void messages(const QString &room_id, const QString &from_token, int limit = 30) noexcept; - void uploadImage(const QString &roomid, const QString &filename); - void uploadFile(const QString &roomid, const QString &filename); - void uploadAudio(const QString &roomid, const QString &filename); + void uploadImage(const QString &roomid, QSharedPointer iodev); + void uploadFile(const QString &roomid, QSharedPointer iodev); + void uploadAudio(const QString &roomid, QSharedPointer iodev); void joinRoom(const QString &roomIdOrAlias); void leaveRoom(const QString &roomId); void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000); @@ -94,9 +94,9 @@ public slots: const QString &homeserver, const QString &token); void versionSuccess(); - void imageUploaded(const QString &roomid, const QString &filename, const QString &url); - void fileUploaded(const QString &roomid, const QString &filename, const QString &url); - void audioUploaded(const QString &roomid, const QString &filename, const QString &url); + void imageUploaded(const QString &roomid, QSharedPointer file, const QString &url); + void fileUploaded(const QString &roomid, QSharedPointer file, const QString &url); + void audioUploaded(const QString &roomid, QSharedPointer file, const QString &url); void roomAvatarRetrieved(const QString &roomid, const QPixmap &img, @@ -123,7 +123,7 @@ public slots: void roomCreationFailed(const QString &msg); private: - QNetworkReply *makeUploadRequest(const QString &filename); + QNetworkReply *makeUploadRequest(QSharedPointer iodev); // Client API prefix. QString clientApiUrl_; diff --git a/include/TextInputWidget.h b/include/TextInputWidget.h index ea3a23fa..814c078a 100644 --- a/include/TextInputWidget.h +++ b/include/TextInputWidget.h @@ -93,9 +93,9 @@ private slots: void sendTextMessage(QString msg); void sendEmoteMessage(QString msg); - void uploadImage(QString filename); - void uploadFile(QString filename); - void uploadAudio(QString filename); + void uploadImage(QSharedPointer file); + void uploadFile(QSharedPointer file); + void uploadAudio(QSharedPointer file); void sendJoinRoomRequest(const QString &room); diff --git a/include/timeline/TimelineView.h b/include/timeline/TimelineView.h index cde64148..473eaa97 100644 --- a/include/timeline/TimelineView.h +++ b/include/timeline/TimelineView.h @@ -43,20 +43,20 @@ struct PendingMessage mtx::events::MessageType ty; int txn_id; QString body; - QString filename; + QSharedPointer file; QString event_id; TimelineItem *widget; PendingMessage(mtx::events::MessageType ty, int txn_id, QString body, - QString filename, + QSharedPointer file, QString event_id, TimelineItem *widget) : ty(ty) , txn_id(txn_id) , body(body) - , filename(filename) + , file(file) , event_id(event_id) , widget(widget) {} @@ -87,7 +87,7 @@ class TimelineView : public QWidget void addUserMessage(mtx::events::MessageType ty, const QString &msg); template - void addUserMessage(const QString &url, const QString &filename); + void addUserMessage(const QString &url, QSharedPointer file); void updatePendingMessage(int txn_id, QString event_id); void scrollDown(); void addDateSeparator(QDateTime datetime, int position); @@ -216,11 +216,11 @@ private slots: template void -TimelineView::addUserMessage(const QString &url, const QString &filename) +TimelineView::addUserMessage(const QString &url, QSharedPointer file) { auto with_sender = lastSender_ != local_user_; - auto widget = new Widget(client_, url, filename, this); + auto widget = new Widget(client_, url, file, this); TimelineItem *view_item = new TimelineItem(widget, local_user_, with_sender, scroll_widget_); @@ -234,7 +234,7 @@ TimelineView::addUserMessage(const QString &url, const QString &filename) int txn_id = client_->incrementTransactionId(); - PendingMessage message(MsgType, txn_id, url, filename, "", view_item); + PendingMessage message(MsgType, txn_id, url, file, "", view_item); handleNewUserMessage(message); } diff --git a/include/timeline/TimelineViewManager.h b/include/timeline/TimelineViewManager.h index 2c32da16..008dd1af 100644 --- a/include/timeline/TimelineViewManager.h +++ b/include/timeline/TimelineViewManager.h @@ -23,6 +23,8 @@ #include +class QFile; + class MatrixClient; class RoomInfoListItem; class TimelineView; @@ -64,9 +66,15 @@ 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); - void queueAudioMessage(const QString &roomid, const QString &filename, const QString &url); + void queueImageMessage(const QString &roomid, + QSharedPointer file, + const QString &url); + void queueFileMessage(const QString &roomid, + QSharedPointer file, + const QString &url); + void queueAudioMessage(const QString &roomid, + QSharedPointer file, + const QString &url); private slots: void messageSent(const QString &eventid, const QString &roomid, int txnid); diff --git a/include/timeline/widgets/AudioItem.h b/include/timeline/widgets/AudioItem.h index f8e7cc07..fd33931f 100644 --- a/include/timeline/widgets/AudioItem.h +++ b/include/timeline/widgets/AudioItem.h @@ -48,7 +48,7 @@ class AudioItem : public QWidget AudioItem(QSharedPointer client, const QString &url, - const QString &filename, + QSharedPointer file, QWidget *parent = nullptr); QSize sizeHint() const override; diff --git a/include/timeline/widgets/FileItem.h b/include/timeline/widgets/FileItem.h index fd0b0249..1c159f10 100644 --- a/include/timeline/widgets/FileItem.h +++ b/include/timeline/widgets/FileItem.h @@ -42,7 +42,7 @@ class FileItem : public QWidget FileItem(QSharedPointer client, const QString &url, - const QString &filename, + QSharedPointer file, QWidget *parent = nullptr); QSize sizeHint() const override; diff --git a/include/timeline/widgets/ImageItem.h b/include/timeline/widgets/ImageItem.h index 467c70ab..30d540c0 100644 --- a/include/timeline/widgets/ImageItem.h +++ b/include/timeline/widgets/ImageItem.h @@ -36,7 +36,7 @@ class ImageItem : public QWidget ImageItem(QSharedPointer client, const QString &url, - const QString &filename, + QSharedPointer file, QWidget *parent = nullptr); void setImage(const QPixmap &image); diff --git a/src/ChatPage.cc b/src/ChatPage.cc index 3958e2c2..3ec5d15a 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -202,16 +202,16 @@ ChatPage::ChatPage(QSharedPointer client, client_.data(), &MatrixClient::joinRoom); - connect(text_input_, &TextInputWidget::uploadImage, this, [=](QString filename) { - client_->uploadImage(current_room_, filename); + connect(text_input_, &TextInputWidget::uploadImage, this, [=](QSharedPointer file) { + client_->uploadImage(current_room_, file); }); - connect(text_input_, &TextInputWidget::uploadFile, this, [=](QString filename) { - client_->uploadFile(current_room_, filename); + connect(text_input_, &TextInputWidget::uploadFile, this, [=](QSharedPointer file) { + client_->uploadFile(current_room_, file); }); - connect(text_input_, &TextInputWidget::uploadAudio, this, [=](QString filename) { - client_->uploadAudio(current_room_, filename); + connect(text_input_, &TextInputWidget::uploadAudio, this, [=](QSharedPointer file) { + client_->uploadAudio(current_room_, file); }); connect( @@ -220,23 +220,23 @@ ChatPage::ChatPage(QSharedPointer client, connect(client_.data(), &MatrixClient::imageUploaded, this, - [=](QString roomid, QString filename, QString url) { + [=](QString roomid, QSharedPointer file, QString url) { text_input_->hideUploadSpinner(); - view_manager_->queueImageMessage(roomid, filename, url); + view_manager_->queueImageMessage(roomid, file, url); }); connect(client_.data(), &MatrixClient::fileUploaded, this, - [=](QString roomid, QString filename, QString url) { + [=](QString roomid, QSharedPointer file, QString url) { text_input_->hideUploadSpinner(); - view_manager_->queueFileMessage(roomid, filename, url); + view_manager_->queueFileMessage(roomid, file, url); }); connect(client_.data(), &MatrixClient::audioUploaded, this, - [=](QString roomid, QString filename, QString url) { + [=](QString roomid, QSharedPointer file, QString url) { text_input_->hideUploadSpinner(); - view_manager_->queueAudioMessage(roomid, filename, url); + view_manager_->queueAudioMessage(roomid, file, url); }); connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar); diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc index 1b2e020d..40869756 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc @@ -675,14 +675,14 @@ MatrixClient::messages(const QString &roomid, const QString &from_token, int lim } void -MatrixClient::uploadImage(const QString &roomid, const QString &filename) +MatrixClient::uploadImage(const QString &roomid, QSharedPointer iodev) { - auto reply = makeUploadRequest(filename); + auto reply = makeUploadRequest(iodev); if (reply == nullptr) return; - connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename]() { + connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, iodev]() { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -711,16 +711,16 @@ MatrixClient::uploadImage(const QString &roomid, const QString &filename) return; } - emit imageUploaded(roomid, filename, object.value("content_uri").toString()); + emit imageUploaded(roomid, iodev.dynamicCast(), object.value("content_uri").toString()); }); } void -MatrixClient::uploadFile(const QString &roomid, const QString &filename) +MatrixClient::uploadFile(const QString &roomid, QSharedPointer iodev) { - auto reply = makeUploadRequest(filename); + auto reply = makeUploadRequest(iodev); - connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename]() { + connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, iodev]() { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -749,16 +749,16 @@ MatrixClient::uploadFile(const QString &roomid, const QString &filename) return; } - emit fileUploaded(roomid, filename, object.value("content_uri").toString()); + emit fileUploaded(roomid, iodev.dynamicCast(), object.value("content_uri").toString()); }); } void -MatrixClient::uploadAudio(const QString &roomid, const QString &filename) +MatrixClient::uploadAudio(const QString &roomid, QSharedPointer iodev) { - auto reply = makeUploadRequest(filename); + auto reply = makeUploadRequest(iodev); - connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename]() { + connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, iodev]() { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -787,7 +787,7 @@ MatrixClient::uploadAudio(const QString &roomid, const QString &filename) return; } - emit audioUploaded(roomid, filename, object.value("content_uri").toString()); + emit audioUploaded(roomid, iodev.dynamicCast(), object.value("content_uri").toString()); }); } @@ -1012,7 +1012,7 @@ MatrixClient::readEvent(const QString &room_id, const QString &event_id) } QNetworkReply * -MatrixClient::makeUploadRequest(const QString &filename) +MatrixClient::makeUploadRequest(QSharedPointer iodev) { QUrlQuery query; query.addQueryItem("access_token", token_); @@ -1021,20 +1021,19 @@ MatrixClient::makeUploadRequest(const QString &filename) endpoint.setPath(mediaApiUrl_ + "/upload"); endpoint.setQuery(query); - QFile file(filename); - if (!file.open(QIODevice::ReadWrite)) { - qDebug() << "Error while reading" << filename; + if (!iodev->open(QIODevice::ReadWrite)) { + qDebug() << "Error while reading buffer" << iodev.data(); return nullptr; } QMimeDatabase db; - QMimeType mime = db.mimeTypeForFile(filename, QMimeDatabase::MatchContent); + QMimeType mime = db.mimeTypeForData(iodev.data()); QNetworkRequest request(QString(endpoint.toEncoded())); - request.setHeader(QNetworkRequest::ContentLengthHeader, file.size()); + request.setHeader(QNetworkRequest::ContentLengthHeader, iodev->size()); request.setHeader(QNetworkRequest::ContentTypeHeader, mime.name()); - auto reply = post(request, file.readAll()); + auto reply = post(request, iodev->readAll()); return reply; } diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc index 141c6566..421989c7 100644 --- a/src/TextInputWidget.cc +++ b/src/TextInputWidget.cc @@ -357,12 +357,13 @@ TextInputWidget::openFileSelection() const auto format = mime.name().split("/")[0]; + QSharedPointer file{new QFile{fileName, this}}; if (format == "image") - emit uploadImage(fileName); + emit uploadImage(file); else if (format == "audio") - emit uploadAudio(fileName); + emit uploadAudio(file); else - emit uploadFile(fileName); + emit uploadFile(file); showUploadSpinner(); } diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc index d21f30f0..066d1df5 100644 --- a/src/timeline/TimelineView.cc +++ b/src/timeline/TimelineView.cc @@ -486,7 +486,7 @@ TimelineView::addUserMessage(mtx::events::MessageType ty, const QString &body) lastSender_ = local_user_; int txn_id = client_->incrementTransactionId(); - PendingMessage message(ty, txn_id, body, "", "", view_item); + PendingMessage message(ty, txn_id, body, nullptr, "", view_item); handleNewUserMessage(message); } @@ -513,8 +513,8 @@ TimelineView::sendNextPendingMessage() client_->sendRoomMessage(m.ty, m.txn_id, room_id_, - QFileInfo(m.filename).fileName(), - QFileInfo(m.filename), + m.file->fileName(), + QFileInfo(*m.file), m.body); break; default: diff --git a/src/timeline/TimelineViewManager.cc b/src/timeline/TimelineViewManager.cc index de1e1e32..eae7091a 100644 --- a/src/timeline/TimelineViewManager.cc +++ b/src/timeline/TimelineViewManager.cc @@ -85,7 +85,7 @@ TimelineViewManager::queueEmoteMessage(const QString &msg) void TimelineViewManager::queueImageMessage(const QString &roomid, - const QString &filename, + QSharedPointer file, const QString &url) { if (!views_.contains(roomid)) { @@ -95,12 +95,12 @@ TimelineViewManager::queueImageMessage(const QString &roomid, auto view = views_[roomid]; - view->addUserMessage(url, filename); + view->addUserMessage(url, file); } void TimelineViewManager::queueFileMessage(const QString &roomid, - const QString &filename, + QSharedPointer file, const QString &url) { if (!views_.contains(roomid)) { @@ -110,12 +110,12 @@ TimelineViewManager::queueFileMessage(const QString &roomid, auto view = views_[roomid]; - view->addUserMessage(url, filename); + view->addUserMessage(url, file); } void TimelineViewManager::queueAudioMessage(const QString &roomid, - const QString &filename, + QSharedPointer file, const QString &url) { if (!views_.contains(roomid)) { @@ -125,7 +125,7 @@ TimelineViewManager::queueAudioMessage(const QString &roomid, auto view = views_[roomid]; - view->addUserMessage(url, filename); + view->addUserMessage(url, file); } void diff --git a/src/timeline/widgets/AudioItem.cc b/src/timeline/widgets/AudioItem.cc index 5d9dd77b..c3c63bb6 100644 --- a/src/timeline/widgets/AudioItem.cc +++ b/src/timeline/widgets/AudioItem.cc @@ -89,14 +89,14 @@ AudioItem::AudioItem(QSharedPointer client, AudioItem::AudioItem(QSharedPointer client, const QString &url, - const QString &filename, + QSharedPointer file, QWidget *parent) : QWidget(parent) , url_{url} - , text_{QFileInfo(filename).fileName()} + , text_{file->fileName()} , client_{client} { - readableFileSize_ = calculateFileSize(QFileInfo(filename).size()); + readableFileSize_ = calculateFileSize(file->size()); init(); } diff --git a/src/timeline/widgets/FileItem.cc b/src/timeline/widgets/FileItem.cc index 3c38dc31..72d20372 100644 --- a/src/timeline/widgets/FileItem.cc +++ b/src/timeline/widgets/FileItem.cc @@ -76,14 +76,14 @@ FileItem::FileItem(QSharedPointer client, FileItem::FileItem(QSharedPointer client, const QString &url, - const QString &filename, + QSharedPointer file, QWidget *parent) : QWidget(parent) , url_{url} - , text_{QFileInfo(filename).fileName()} + , text_{file->fileName()} , client_{client} { - readableFileSize_ = calculateFileSize(QFileInfo(filename).size()); + readableFileSize_ = calculateFileSize(file->size()); init(); } diff --git a/src/timeline/widgets/ImageItem.cc b/src/timeline/widgets/ImageItem.cc index 9038456d..75d53a17 100644 --- a/src/timeline/widgets/ImageItem.cc +++ b/src/timeline/widgets/ImageItem.cc @@ -61,11 +61,11 @@ ImageItem::ImageItem(QSharedPointer client, ImageItem::ImageItem(QSharedPointer client, const QString &url, - const QString &filename, + QSharedPointer file, QWidget *parent) : QWidget(parent) , url_{url} - , text_{QFileInfo(filename).fileName()} + , text_{file->fileName()} , client_{client} { setMouseTracking(true); @@ -83,7 +83,7 @@ ImageItem::ImageItem(QSharedPointer client, url_ = QString("%1/_matrix/media/r0/download/%2") .arg(client_.data()->getHomeServer().toString(), media_params); - setImage(QPixmap(filename)); + setImage(QPixmap(file->fileName())); } void From 47a27ca5b75b75fb3e6689b87e87910b0c8d2460 Mon Sep 17 00:00:00 2001 From: Chris Tarazi Date: Sun, 24 Dec 2017 11:02:31 -0800 Subject: [PATCH 3/8] Use new QIODevice api for pasted images --- include/TextInputWidget.h | 2 +- src/TextInputWidget.cc | 19 +++++++------------ src/dialogs/PreviewImageOverlay.cc | 4 ++-- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/include/TextInputWidget.h b/include/TextInputWidget.h index 814c078a..115d336b 100644 --- a/include/TextInputWidget.h +++ b/include/TextInputWidget.h @@ -52,7 +52,7 @@ class FilteredTextEdit : public QTextEdit void stoppedTyping(); void message(QString); void command(QString name, QString args); - void image(QString name); + void image(QSharedPointer file); protected: void keyPressEvent(QKeyEvent *event) override; diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc index 421989c7..2cc2898b 100644 --- a/src/TextInputWidget.cc +++ b/src/TextInputWidget.cc @@ -115,10 +115,7 @@ FilteredTextEdit::canInsertFromMimeData(const QMimeData *source) const void FilteredTextEdit::insertFromMimeData(const QMimeData *source) { - qDebug() << "insertFromMimeData source->text():" << source->text(); - if (source->hasImage()) { - qDebug() << "PreviewImageOverlay show"; QPixmap const img = qvariant_cast(source->imageData()); previewDialog_ = new dialogs::PreviewImageOverlay{img, this}; connect(previewDialog_, @@ -130,7 +127,6 @@ FilteredTextEdit::insertFromMimeData(const QMimeData *source) QImageReader{source->text()}.canRead()) { // Special case for X11 users. See "Notes for X11 Users" in source. // Source: http://doc.qt.io/qt-5/qclipboard.html - qDebug() << "PreviewImageOverlay special gnome show"; previewDialog_ = new dialogs::PreviewImageOverlay{source->text(), this}; connect(previewDialog_, &dialogs::PreviewImageOverlay::confirmImageUpload, @@ -215,24 +211,23 @@ FilteredTextEdit::textChanged() void FilteredTextEdit::receiveImage(const QPixmap &img, const QString &img_name) { - qDebug() << "Received image from PreviewImageOverlay:" << img; - // Save image into temporary path to be loaded later. QString img_path = QDir::tempPath() + '/' + img_name; - QFile file{img_path}; + QSharedPointer file{new QFile{img_path, this}}; - if (!file.open(QIODevice::WriteOnly)) { - qDebug() << "Failed to create temporary image file"; + if (!file->open(QIODevice::WriteOnly)) { + qDebug() << "Failed to open temporary image file:" << file->errorString(); previewDialog_->close(); return; } - if (!img.save(file.fileName(), "PNG")) { - qDebug() << "Failed to save image data"; + if (!img.save(file->fileName(), "PNG")) { + qDebug() << "Failed to save image data to:" << file->fileName(); previewDialog_->close(); return; } + file->close(); - emit image(img_path); + emit image(file); previewDialog_->close(); } diff --git a/src/dialogs/PreviewImageOverlay.cc b/src/dialogs/PreviewImageOverlay.cc index 3edc55ae..f9b54d9c 100644 --- a/src/dialogs/PreviewImageOverlay.cc +++ b/src/dialogs/PreviewImageOverlay.cc @@ -96,10 +96,10 @@ PreviewImageOverlay::init() vlayout->addWidget(imageName_); vlayout->addLayout(hlayout); - connect(upload_, &QPushButton::clicked, [=]() { + connect(upload_, &QPushButton::clicked, [&]() { emit confirmImageUpload(image_, imageName_->text()); }); - connect(cancel_, &QPushButton::clicked, &dialogs::PreviewImageOverlay::close); + connect(cancel_, &QPushButton::clicked, [&]() { close(); }); raise(); } From 6190254ae0355f9149d49ff486eb7d45ddaec73f Mon Sep 17 00:00:00 2001 From: Chris Tarazi Date: Wed, 27 Dec 2017 22:56:46 -0800 Subject: [PATCH 4/8] Address the issues pointed out in review - Image data no longer written to disk; only in memory - Fix incorrect type used in qvariant_cast - PreviewImageOverlay is no longer dynamic; created on stack - Images are encoded based on MIME data; default is PNG - Handle images that are not on disk in ImageItem - Close PreviewImageOverlay dialog after upload button click - Follow consistent dialog styles in PreviewImageOverlay --- include/MatrixClient.h | 21 +++-- include/TextInputWidget.h | 14 +-- include/dialogs/PreviewImageOverlay.h | 28 +++--- include/timeline/TimelineView.h | 18 ++-- include/timeline/TimelineViewManager.h | 11 +-- include/timeline/widgets/AudioItem.h | 3 +- include/timeline/widgets/FileItem.h | 3 +- include/timeline/widgets/ImageItem.h | 3 +- include/timeline/widgets/VideoItem.h | 1 + src/ChatPage.cc | 39 ++++---- src/MatrixClient.cc | 36 ++++---- src/TextInputWidget.cc | 77 ++++++++-------- src/dialogs/PreviewImageOverlay.cc | 118 ++++++++++++++----------- src/timeline/TimelineView.cc | 6 +- src/timeline/TimelineViewManager.cc | 13 +-- src/timeline/widgets/AudioItem.cc | 8 +- src/timeline/widgets/FileItem.cc | 8 +- src/timeline/widgets/ImageItem.cc | 14 ++- src/timeline/widgets/VideoItem.cc | 2 + 19 files changed, 246 insertions(+), 177 deletions(-) diff --git a/include/MatrixClient.h b/include/MatrixClient.h index 3d477b9f..fb015fdf 100644 --- a/include/MatrixClient.h +++ b/include/MatrixClient.h @@ -52,9 +52,15 @@ class MatrixClient : public QNetworkAccessManager void downloadImage(const QString &event_id, const QUrl &url); void downloadFile(const QString &event_id, const QUrl &url); void messages(const QString &room_id, const QString &from_token, int limit = 30) noexcept; - void uploadImage(const QString &roomid, QSharedPointer iodev); - void uploadFile(const QString &roomid, QSharedPointer iodev); - void uploadAudio(const QString &roomid, QSharedPointer iodev); + void uploadImage(const QString &roomid, + const QSharedPointer data, + const QString &filename); + void uploadFile(const QString &roomid, + const QSharedPointer data, + const QString &filename); + void uploadAudio(const QString &roomid, + const QSharedPointer data, + const QString &filename); void joinRoom(const QString &roomIdOrAlias); void leaveRoom(const QString &roomId); void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000); @@ -94,9 +100,12 @@ public slots: const QString &homeserver, const QString &token); void versionSuccess(); - void imageUploaded(const QString &roomid, QSharedPointer file, const QString &url); - void fileUploaded(const QString &roomid, QSharedPointer file, const QString &url); - void audioUploaded(const QString &roomid, QSharedPointer file, const QString &url); + void imageUploaded(const QString &roomid, + const QSharedPointer data, + const QString &filename, + const QString &url); + void fileUploaded(const QString &roomid, const QString &filename, const QString &url); + void audioUploaded(const QString &roomid, const QString &filename, const QString &url); void roomAvatarRetrieved(const QString &roomid, const QPixmap &img, diff --git a/include/TextInputWidget.h b/include/TextInputWidget.h index 115d336b..3d28b943 100644 --- a/include/TextInputWidget.h +++ b/include/TextInputWidget.h @@ -27,6 +27,8 @@ #include "FlatButton.h" #include "LoadingIndicator.h" +#include "dialogs/PreviewImageOverlay.h" + #include "emoji/PickButton.h" namespace dialogs { @@ -52,7 +54,7 @@ class FilteredTextEdit : public QTextEdit void stoppedTyping(); void message(QString); void command(QString name, QString args); - void image(QSharedPointer file); + void image(const QSharedPointer iodev, const QString &img_name); protected: void keyPressEvent(QKeyEvent *event) override; @@ -64,10 +66,10 @@ class FilteredTextEdit : public QTextEdit size_t history_index_; QTimer *typingTimer_; - dialogs::PreviewImageOverlay *previewDialog_; + dialogs::PreviewImageOverlay previewDialog_; void textChanged(); - void receiveImage(const QPixmap &img, const QString &img_name); + void receiveImage(const QByteArray &img, const QString &img_name); void afterCompletion(int); }; @@ -93,9 +95,9 @@ private slots: void sendTextMessage(QString msg); void sendEmoteMessage(QString msg); - void uploadImage(QSharedPointer file); - void uploadFile(QSharedPointer file); - void uploadAudio(QSharedPointer file); + void uploadImage(QSharedPointer data, const QString &filename); + void uploadFile(QSharedPointer data, const QString &filename); + void uploadAudio(QSharedPointer data, const QString &filename); void sendJoinRoomRequest(const QString &room); diff --git a/include/dialogs/PreviewImageOverlay.h b/include/dialogs/PreviewImageOverlay.h index 45692dc5..ff2e1ce8 100644 --- a/include/dialogs/PreviewImageOverlay.h +++ b/include/dialogs/PreviewImageOverlay.h @@ -17,12 +17,14 @@ #pragma once +#include +#include #include #include -class QLabel; -class QLineEdit; -class QPushButton; +#include "FlatButton.h" + +class QMimeData; namespace dialogs { @@ -30,22 +32,26 @@ class PreviewImageOverlay : public QWidget { Q_OBJECT public: - PreviewImageOverlay(QPixmap image, QWidget *parent = nullptr); - PreviewImageOverlay(const QString &path, QWidget *parent = nullptr); + PreviewImageOverlay(QWidget *parent = nullptr); + + void setImageAndCreate(const QByteArray &data, const QString &type); + void setImageAndCreate(const QString &path); signals: - void confirmImageUpload(const QPixmap &img, const QString &img_name); + void confirmImageUpload(const QByteArray &data, const QString &img_name); private: void init(); QPixmap image_; + QByteArray imageData_; + QString imagePath_; - QLabel *titleLabel_; - QLabel *imageLabel_; - QLineEdit *imageName_; + QLabel titleLabel_; + QLabel imageLabel_; + QLineEdit imageName_; - QPushButton *upload_; - QPushButton *cancel_; + FlatButton upload_; + FlatButton cancel_; }; } // dialogs diff --git a/include/timeline/TimelineView.h b/include/timeline/TimelineView.h index 473eaa97..77eaa0d2 100644 --- a/include/timeline/TimelineView.h +++ b/include/timeline/TimelineView.h @@ -43,20 +43,20 @@ struct PendingMessage mtx::events::MessageType ty; int txn_id; QString body; - QSharedPointer file; + QString filename; QString event_id; TimelineItem *widget; PendingMessage(mtx::events::MessageType ty, int txn_id, QString body, - QSharedPointer file, + const QString &fn, QString event_id, TimelineItem *widget) : ty(ty) , txn_id(txn_id) , body(body) - , file(file) + , filename(fn) , event_id(event_id) , widget(widget) {} @@ -87,7 +87,9 @@ class TimelineView : public QWidget void addUserMessage(mtx::events::MessageType ty, const QString &msg); template - void addUserMessage(const QString &url, QSharedPointer file); + void addUserMessage(const QString &url, + const QSharedPointer data, + const QString &filename); void updatePendingMessage(int txn_id, QString event_id); void scrollDown(); void addDateSeparator(QDateTime datetime, int position); @@ -216,11 +218,13 @@ private slots: template void -TimelineView::addUserMessage(const QString &url, QSharedPointer file) +TimelineView::addUserMessage(const QString &url, + const QSharedPointer data, + const QString &filename) { auto with_sender = lastSender_ != local_user_; - auto widget = new Widget(client_, url, file, this); + auto widget = new Widget(client_, url, data, filename, this); TimelineItem *view_item = new TimelineItem(widget, local_user_, with_sender, scroll_widget_); @@ -234,7 +238,7 @@ TimelineView::addUserMessage(const QString &url, QSharedPointer file) int txn_id = client_->incrementTransactionId(); - PendingMessage message(MsgType, txn_id, url, file, "", view_item); + PendingMessage message(MsgType, txn_id, url, filename, "", view_item); handleNewUserMessage(message); } diff --git a/include/timeline/TimelineViewManager.h b/include/timeline/TimelineViewManager.h index 008dd1af..c19031c6 100644 --- a/include/timeline/TimelineViewManager.h +++ b/include/timeline/TimelineViewManager.h @@ -67,14 +67,11 @@ public slots: void queueTextMessage(const QString &msg); void queueEmoteMessage(const QString &msg); void queueImageMessage(const QString &roomid, - QSharedPointer file, - const QString &url); - void queueFileMessage(const QString &roomid, - QSharedPointer file, - const QString &url); - void queueAudioMessage(const QString &roomid, - QSharedPointer file, + const QSharedPointer data, + const QString &filename, const QString &url); + void queueFileMessage(const QString &roomid, const QString &filename, const QString &url); + void queueAudioMessage(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/timeline/widgets/AudioItem.h b/include/timeline/widgets/AudioItem.h index fd33931f..ca81f498 100644 --- a/include/timeline/widgets/AudioItem.h +++ b/include/timeline/widgets/AudioItem.h @@ -48,7 +48,8 @@ class AudioItem : public QWidget AudioItem(QSharedPointer client, const QString &url, - QSharedPointer file, + const QSharedPointer data, + const QString &filename, QWidget *parent = nullptr); QSize sizeHint() const override; diff --git a/include/timeline/widgets/FileItem.h b/include/timeline/widgets/FileItem.h index 1c159f10..72589189 100644 --- a/include/timeline/widgets/FileItem.h +++ b/include/timeline/widgets/FileItem.h @@ -42,7 +42,8 @@ class FileItem : public QWidget FileItem(QSharedPointer client, const QString &url, - QSharedPointer file, + const QSharedPointer data, + const QString &filename, QWidget *parent = nullptr); QSize sizeHint() const override; diff --git a/include/timeline/widgets/ImageItem.h b/include/timeline/widgets/ImageItem.h index 30d540c0..d24b7239 100644 --- a/include/timeline/widgets/ImageItem.h +++ b/include/timeline/widgets/ImageItem.h @@ -36,7 +36,8 @@ class ImageItem : public QWidget ImageItem(QSharedPointer client, const QString &url, - QSharedPointer file, + const QSharedPointer data, + const QString &filename, QWidget *parent = nullptr); void setImage(const QPixmap &image); diff --git a/include/timeline/widgets/VideoItem.h b/include/timeline/widgets/VideoItem.h index 88ff21ec..53c3e21a 100644 --- a/include/timeline/widgets/VideoItem.h +++ b/include/timeline/widgets/VideoItem.h @@ -37,6 +37,7 @@ class VideoItem : public QWidget VideoItem(QSharedPointer client, const QString &url, + const QSharedPointer data, const QString &filename, QWidget *parent = nullptr); diff --git a/src/ChatPage.cc b/src/ChatPage.cc index 3ec5d15a..4f686516 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -202,17 +202,26 @@ ChatPage::ChatPage(QSharedPointer client, client_.data(), &MatrixClient::joinRoom); - connect(text_input_, &TextInputWidget::uploadImage, this, [=](QSharedPointer file) { - client_->uploadImage(current_room_, file); - }); + connect(text_input_, + &TextInputWidget::uploadImage, + this, + [=](QSharedPointer data, const QString &fn) { + client_->uploadImage(current_room_, data, fn); + }); - connect(text_input_, &TextInputWidget::uploadFile, this, [=](QSharedPointer file) { - client_->uploadFile(current_room_, file); - }); + connect(text_input_, + &TextInputWidget::uploadFile, + this, + [=](QSharedPointer data, const QString &fn) { + client_->uploadFile(current_room_, data, fn); + }); - connect(text_input_, &TextInputWidget::uploadAudio, this, [=](QSharedPointer file) { - client_->uploadAudio(current_room_, file); - }); + connect(text_input_, + &TextInputWidget::uploadAudio, + this, + [=](QSharedPointer data, const QString &fn) { + client_->uploadAudio(current_room_, data, fn); + }); connect( client_.data(), &MatrixClient::roomCreationFailed, this, &ChatPage::showNotification); @@ -220,23 +229,23 @@ ChatPage::ChatPage(QSharedPointer client, connect(client_.data(), &MatrixClient::imageUploaded, this, - [=](QString roomid, QSharedPointer file, QString url) { + [=](QString roomid, QSharedPointer data, QString filename, QString url) { text_input_->hideUploadSpinner(); - view_manager_->queueImageMessage(roomid, file, url); + view_manager_->queueImageMessage(roomid, data, filename, url); }); connect(client_.data(), &MatrixClient::fileUploaded, this, - [=](QString roomid, QSharedPointer file, QString url) { + [=](QString roomid, QString filename, QString url) { text_input_->hideUploadSpinner(); - view_manager_->queueFileMessage(roomid, file, url); + view_manager_->queueFileMessage(roomid, filename, url); }); connect(client_.data(), &MatrixClient::audioUploaded, this, - [=](QString roomid, QSharedPointer file, QString url) { + [=](QString roomid, QString filename, QString url) { text_input_->hideUploadSpinner(); - view_manager_->queueAudioMessage(roomid, file, url); + view_manager_->queueAudioMessage(roomid, filename, url); }); connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar); diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc index 40869756..53d725c6 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc @@ -675,14 +675,16 @@ MatrixClient::messages(const QString &roomid, const QString &from_token, int lim } void -MatrixClient::uploadImage(const QString &roomid, QSharedPointer iodev) +MatrixClient::uploadImage(const QString &roomid, + const QSharedPointer data, + const QString &filename) { - auto reply = makeUploadRequest(iodev); + auto reply = makeUploadRequest(data); if (reply == nullptr) return; - connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, iodev]() { + connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, data, filename]() { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -692,12 +694,12 @@ MatrixClient::uploadImage(const QString &roomid, QSharedPointer iodev return; } - auto data = reply->readAll(); + auto res_data = reply->readAll(); - if (data.isEmpty()) + if (res_data.isEmpty()) return; - auto json = QJsonDocument::fromJson(data); + auto json = QJsonDocument::fromJson(res_data); if (!json.isObject()) { qDebug() << "Media upload: Response is not a json object."; @@ -711,16 +713,18 @@ MatrixClient::uploadImage(const QString &roomid, QSharedPointer iodev return; } - emit imageUploaded(roomid, iodev.dynamicCast(), object.value("content_uri").toString()); + emit imageUploaded(roomid, data, filename, object.value("content_uri").toString()); }); } void -MatrixClient::uploadFile(const QString &roomid, QSharedPointer iodev) +MatrixClient::uploadFile(const QString &roomid, + const QSharedPointer data, + const QString &filename) { - auto reply = makeUploadRequest(iodev); + auto reply = makeUploadRequest(data); - connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, iodev]() { + connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename]() { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -749,16 +753,18 @@ MatrixClient::uploadFile(const QString &roomid, QSharedPointer iodev) return; } - emit fileUploaded(roomid, iodev.dynamicCast(), object.value("content_uri").toString()); + emit fileUploaded(roomid, filename, object.value("content_uri").toString()); }); } void -MatrixClient::uploadAudio(const QString &roomid, QSharedPointer iodev) +MatrixClient::uploadAudio(const QString &roomid, + const QSharedPointer data, + const QString &filename) { - auto reply = makeUploadRequest(iodev); + auto reply = makeUploadRequest(data); - connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, iodev]() { + connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename]() { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -787,7 +793,7 @@ MatrixClient::uploadAudio(const QString &roomid, QSharedPointer iodev return; } - emit audioUploaded(roomid, iodev.dynamicCast(), object.value("content_uri").toString()); + emit audioUploaded(roomid, filename, object.value("content_uri").toString()); }); } diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc index 2cc2898b..d1c56c09 100644 --- a/src/TextInputWidget.cc +++ b/src/TextInputWidget.cc @@ -17,9 +17,9 @@ #include #include +#include #include #include -#include #include #include #include @@ -30,13 +30,13 @@ #include "Config.h" #include "TextInputWidget.h" -#include "dialogs/PreviewImageOverlay.h" static constexpr size_t INPUT_HISTORY_SIZE = 127; FilteredTextEdit::FilteredTextEdit(QWidget *parent) : QTextEdit{parent} , history_index_{0} + , previewDialog_{parent} { connect(document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, @@ -54,6 +54,12 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent) typingTimer_->setSingleShot(true); connect(typingTimer_, &QTimer::timeout, this, &FilteredTextEdit::stopTyping); + connect(&previewDialog_, + &dialogs::PreviewImageOverlay::confirmImageUpload, + this, + &FilteredTextEdit::receiveImage); + + previewDialog_.hide(); } void @@ -116,23 +122,35 @@ void FilteredTextEdit::insertFromMimeData(const QMimeData *source) { if (source->hasImage()) { - QPixmap const img = qvariant_cast(source->imageData()); - previewDialog_ = new dialogs::PreviewImageOverlay{img, this}; - connect(previewDialog_, - &dialogs::PreviewImageOverlay::confirmImageUpload, - this, - &FilteredTextEdit::receiveImage); - previewDialog_->show(); + const QImage image = qvariant_cast(source->imageData()); + + const auto formats = source->formats(); + const auto idx = formats.indexOf( + QRegularExpression{"image/\\w+", QRegularExpression::CaseInsensitiveOption}); + + // Defaulting to PNG format. + QString type = "png"; + if (idx != -1) { + type = formats.at(idx).split('/')[1]; + } + + // Encode raw pixel data of image. + QByteArray data; + QBuffer buffer(&data); + buffer.open(QIODevice::ReadWrite); + if (!image.save(&buffer, type.toLocal8Bit().data())) { + qWarning() << "Failed to encode image into" << type; + return; + } + + previewDialog_.setImageAndCreate(data, type); + previewDialog_.show(); } else if (source->hasFormat("x-special/gnome-copied-files") && QImageReader{source->text()}.canRead()) { // Special case for X11 users. See "Notes for X11 Users" in source. // Source: http://doc.qt.io/qt-5/qclipboard.html - previewDialog_ = new dialogs::PreviewImageOverlay{source->text(), this}; - connect(previewDialog_, - &dialogs::PreviewImageOverlay::confirmImageUpload, - this, - &FilteredTextEdit::receiveImage); - previewDialog_->show(); + previewDialog_.setImageAndCreate(source->text()); + previewDialog_.show(); } else { QTextEdit::insertFromMimeData(source); } @@ -209,27 +227,10 @@ FilteredTextEdit::textChanged() } void -FilteredTextEdit::receiveImage(const QPixmap &img, const QString &img_name) +FilteredTextEdit::receiveImage(const QByteArray &img, const QString &img_name) { - // Save image into temporary path to be loaded later. - QString img_path = QDir::tempPath() + '/' + img_name; - QSharedPointer file{new QFile{img_path, this}}; - - if (!file->open(QIODevice::WriteOnly)) { - qDebug() << "Failed to open temporary image file:" << file->errorString(); - previewDialog_->close(); - return; - } - if (!img.save(file->fileName(), "PNG")) { - qDebug() << "Failed to save image data to:" << file->fileName(); - previewDialog_->close(); - return; - } - file->close(); - - emit image(file); - - previewDialog_->close(); + QSharedPointer buffer{new QBuffer{const_cast(&img), this}}; + emit image(buffer, img_name); } TextInputWidget::TextInputWidget(QWidget *parent) @@ -354,11 +355,11 @@ TextInputWidget::openFileSelection() QSharedPointer file{new QFile{fileName, this}}; if (format == "image") - emit uploadImage(file); + emit uploadImage(file, fileName); else if (format == "audio") - emit uploadAudio(file); + emit uploadAudio(file, fileName); else - emit uploadFile(file); + emit uploadFile(file, fileName); showUploadSpinner(); } diff --git a/src/dialogs/PreviewImageOverlay.cc b/src/dialogs/PreviewImageOverlay.cc index f9b54d9c..58317ca4 100644 --- a/src/dialogs/PreviewImageOverlay.cc +++ b/src/dialogs/PreviewImageOverlay.cc @@ -16,36 +16,41 @@ */ #include +#include #include -#include +#include #include -#include -#include -#include -#include #include +#include "Config.h" + #include "dialogs/PreviewImageOverlay.h" using namespace dialogs; -PreviewImageOverlay::PreviewImageOverlay(QPixmap image, QWidget *parent) +PreviewImageOverlay::PreviewImageOverlay(QWidget *parent) : QWidget{parent} - , image_{image} + , titleLabel_{tr("Upload image?"), this} + , imageLabel_{this} + , imageName_{tr("clipboard"), this} + , upload_{tr("Upload"), this} + , cancel_{tr("Cancel"), this} { - init(); -} + auto hlayout = new QHBoxLayout; + hlayout->addWidget(&upload_); + hlayout->addWidget(&cancel_); -PreviewImageOverlay::PreviewImageOverlay(const QString &path, QWidget *parent) - : QWidget{parent} -{ - if (!image_.load(path)) { - qDebug() << "Failed to read image from:" << path; - close(); - return; - } + auto vlayout = new QVBoxLayout{this}; + vlayout->addWidget(&titleLabel_); + vlayout->addWidget(&imageLabel_); + vlayout->addWidget(&imageName_); + vlayout->addLayout(hlayout); - init(); + connect(&upload_, &QPushButton::clicked, [&]() { + emit confirmImageUpload(imageData_, imageName_.text()); + close(); + }); + connect(&cancel_, &QPushButton::clicked, [&]() { close(); }); } void @@ -56,50 +61,61 @@ PreviewImageOverlay::init() auto center = window->frameGeometry().center(); auto img_size = image_.size(); + imageName_.setText(imagePath_); + setAutoFillBackground(true); setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); setWindowModality(Qt::WindowModal); - setPalette(QApplication::palette(window)); - - titleLabel_ = new QLabel{tr("Upload image?"), this}; - imageLabel_ = new QLabel{this}; - imageName_ = new QLineEdit{tr("nheko_pasted_img.png"), this}; - upload_ = new QPushButton{tr("Upload"), this}; - cancel_ = new QPushButton{tr("Cancel"), this}; - - titleLabel_->setStyleSheet("font-weight: bold; font-size: 22px;"); - titleLabel_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - titleLabel_->setAlignment(Qt::AlignCenter); - imageLabel_->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - imageLabel_->setAlignment(Qt::AlignCenter); - imageName_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - imageName_->setAlignment(Qt::AlignCenter); - upload_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - cancel_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + titleLabel_.setStyleSheet( + QString{"font-weight: bold; font-size: %1px;"}.arg(conf::headerFontSize)); + titleLabel_.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + titleLabel_.setAlignment(Qt::AlignCenter); + imageLabel_.setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + imageLabel_.setAlignment(Qt::AlignCenter); + imageName_.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + imageName_.setAlignment(Qt::AlignCenter); + upload_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + cancel_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + upload_.setFontSize(conf::btn::fontSize); + cancel_.setFontSize(conf::btn::fontSize); // Scale image preview to the size of the current window if it is larger. if ((img_size.height() * img_size.width()) > (winsize.height() * winsize.width())) { - imageLabel_->setPixmap(image_.scaled(winsize, Qt::KeepAspectRatio)); + imageLabel_.setPixmap(image_.scaled(winsize, Qt::KeepAspectRatio)); } else { - imageLabel_->setPixmap(image_); + imageLabel_.setPixmap(image_); move(center.x() - (width() * 0.5), center.y() - (height() * 0.5)); } - imageLabel_->setScaledContents(false); + imageLabel_.setScaledContents(false); - auto hlayout = new QHBoxLayout; - hlayout->addWidget(upload_); - hlayout->addWidget(cancel_); + raise(); +} - auto vlayout = new QVBoxLayout{this}; - vlayout->addWidget(titleLabel_); - vlayout->addWidget(imageLabel_); - vlayout->addWidget(imageName_); - vlayout->addLayout(hlayout); +void +PreviewImageOverlay::setImageAndCreate(const QByteArray &data, const QString &type) +{ + imageData_ = data; + image_.loadFromData(imageData_); + imagePath_ = "clipboard." + type; - connect(upload_, &QPushButton::clicked, [&]() { - emit confirmImageUpload(image_, imageName_->text()); - }); - connect(cancel_, &QPushButton::clicked, [&]() { close(); }); + init(); +} - raise(); +void +PreviewImageOverlay::setImageAndCreate(const QString &path) +{ + QFile file{path}; + + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << "Failed to read image from:" << path; + close(); + return; + } + + imageData_ = file.readAll(); + image_.loadFromData(imageData_); + imagePath_ = path; + + init(); } diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc index 066d1df5..29929cdd 100644 --- a/src/timeline/TimelineView.cc +++ b/src/timeline/TimelineView.cc @@ -486,7 +486,7 @@ TimelineView::addUserMessage(mtx::events::MessageType ty, const QString &body) lastSender_ = local_user_; int txn_id = client_->incrementTransactionId(); - PendingMessage message(ty, txn_id, body, nullptr, "", view_item); + PendingMessage message(ty, txn_id, body, "", "", view_item); handleNewUserMessage(message); } @@ -513,8 +513,8 @@ TimelineView::sendNextPendingMessage() client_->sendRoomMessage(m.ty, m.txn_id, room_id_, - m.file->fileName(), - QFileInfo(*m.file), + m.filename, + QFileInfo(m.filename), m.body); break; default: diff --git a/src/timeline/TimelineViewManager.cc b/src/timeline/TimelineViewManager.cc index eae7091a..65c9ac83 100644 --- a/src/timeline/TimelineViewManager.cc +++ b/src/timeline/TimelineViewManager.cc @@ -85,7 +85,8 @@ TimelineViewManager::queueEmoteMessage(const QString &msg) void TimelineViewManager::queueImageMessage(const QString &roomid, - QSharedPointer file, + const QSharedPointer data, + const QString &filename, const QString &url) { if (!views_.contains(roomid)) { @@ -95,12 +96,12 @@ TimelineViewManager::queueImageMessage(const QString &roomid, auto view = views_[roomid]; - view->addUserMessage(url, file); + view->addUserMessage(url, data, filename); } void TimelineViewManager::queueFileMessage(const QString &roomid, - QSharedPointer file, + const QString &filename, const QString &url) { if (!views_.contains(roomid)) { @@ -110,12 +111,12 @@ TimelineViewManager::queueFileMessage(const QString &roomid, auto view = views_[roomid]; - view->addUserMessage(url, file); + view->addUserMessage(url, nullptr, filename); } void TimelineViewManager::queueAudioMessage(const QString &roomid, - QSharedPointer file, + const QString &filename, const QString &url) { if (!views_.contains(roomid)) { @@ -125,7 +126,7 @@ TimelineViewManager::queueAudioMessage(const QString &roomid, auto view = views_[roomid]; - view->addUserMessage(url, file); + view->addUserMessage(url, nullptr, filename); } void diff --git a/src/timeline/widgets/AudioItem.cc b/src/timeline/widgets/AudioItem.cc index c3c63bb6..e84cbb3a 100644 --- a/src/timeline/widgets/AudioItem.cc +++ b/src/timeline/widgets/AudioItem.cc @@ -89,14 +89,16 @@ AudioItem::AudioItem(QSharedPointer client, AudioItem::AudioItem(QSharedPointer client, const QString &url, - QSharedPointer file, + const QSharedPointer data, + const QString &filename, QWidget *parent) : QWidget(parent) , url_{url} - , text_{file->fileName()} + , text_{QFileInfo{filename}.fileName()} , client_{client} { - readableFileSize_ = calculateFileSize(file->size()); + Q_UNUSED(data); + readableFileSize_ = calculateFileSize(QFileInfo{filename}.size()); init(); } diff --git a/src/timeline/widgets/FileItem.cc b/src/timeline/widgets/FileItem.cc index 72d20372..a6159309 100644 --- a/src/timeline/widgets/FileItem.cc +++ b/src/timeline/widgets/FileItem.cc @@ -76,14 +76,16 @@ FileItem::FileItem(QSharedPointer client, FileItem::FileItem(QSharedPointer client, const QString &url, - QSharedPointer file, + const QSharedPointer data, + const QString &filename, QWidget *parent) : QWidget(parent) , url_{url} - , text_{file->fileName()} + , text_{QFileInfo{filename}.fileName()} , client_{client} { - readableFileSize_ = calculateFileSize(file->size()); + Q_UNUSED(data); + readableFileSize_ = calculateFileSize(QFileInfo{filename}.size()); init(); } diff --git a/src/timeline/widgets/ImageItem.cc b/src/timeline/widgets/ImageItem.cc index 75d53a17..cdccff71 100644 --- a/src/timeline/widgets/ImageItem.cc +++ b/src/timeline/widgets/ImageItem.cc @@ -61,11 +61,12 @@ ImageItem::ImageItem(QSharedPointer client, ImageItem::ImageItem(QSharedPointer client, const QString &url, - QSharedPointer file, + const QSharedPointer data, + const QString &filename, QWidget *parent) : QWidget(parent) , url_{url} - , text_{file->fileName()} + , text_{QFileInfo{filename}.fileName()} , client_{client} { setMouseTracking(true); @@ -83,7 +84,14 @@ ImageItem::ImageItem(QSharedPointer client, url_ = QString("%1/_matrix/media/r0/download/%2") .arg(client_.data()->getHomeServer().toString(), media_params); - setImage(QPixmap(file->fileName())); + if (!data.isNull()) { + data->reset(); + QPixmap p; + p.loadFromData(data->readAll()); + setImage(p); + } else { + setImage(QPixmap(filename)); + } } void diff --git a/src/timeline/widgets/VideoItem.cc b/src/timeline/widgets/VideoItem.cc index b3987b83..b46dff7b 100644 --- a/src/timeline/widgets/VideoItem.cc +++ b/src/timeline/widgets/VideoItem.cc @@ -66,6 +66,7 @@ VideoItem::VideoItem(QSharedPointer client, VideoItem::VideoItem(QSharedPointer client, const QString &url, + const QSharedPointer data, const QString &filename, QWidget *parent) : QWidget(parent) @@ -73,6 +74,7 @@ VideoItem::VideoItem(QSharedPointer client, , text_{QFileInfo(filename).fileName()} , client_{client} { + Q_UNUSED(data); readableFileSize_ = calculateFileSize(QFileInfo(filename).size()); init(); From 334d73c0242c9a245c8019ee116f66f350073a88 Mon Sep 17 00:00:00 2001 From: Chris Tarazi Date: Tue, 2 Jan 2018 22:28:37 -0800 Subject: [PATCH 5/8] Address the issues pointed out in review (2) - Grab encoded image data directly from clipboard rather than explicity transcoding. - Fix inadvertant deletion in styles - Pass pointer to QNetworkAccessManager::post instead of reading all the data at once - Remove unnecessary use of const_cast - Use pass-by-value for QByteArray - Log reason for QIODevice failing to open - Add missing check of return value for QIODevice::reset - Do not create QFileInfo on nonexistent file - Add note about MIME image data - Only show filename inside PreviewImageOverlay rather than path - Add error message to PreviewImageOverlay on failed read --- include/TextInputWidget.h | 2 +- include/dialogs/PreviewImageOverlay.h | 4 +-- resources/styles/nheko-dark.qss | 4 +-- src/MatrixClient.cc | 10 +++---- src/TextInputWidget.cc | 19 ++++--------- src/dialogs/PreviewImageOverlay.cc | 41 ++++++++++++++++++++------- src/timeline/widgets/ImageItem.cc | 14 +++++---- 7 files changed, 55 insertions(+), 39 deletions(-) diff --git a/include/TextInputWidget.h b/include/TextInputWidget.h index 3d28b943..cc01be69 100644 --- a/include/TextInputWidget.h +++ b/include/TextInputWidget.h @@ -69,7 +69,7 @@ class FilteredTextEdit : public QTextEdit dialogs::PreviewImageOverlay previewDialog_; void textChanged(); - void receiveImage(const QByteArray &img, const QString &img_name); + void receiveImage(const QByteArray img, const QString &img_name); void afterCompletion(int); }; diff --git a/include/dialogs/PreviewImageOverlay.h b/include/dialogs/PreviewImageOverlay.h index ff2e1ce8..a1ab32ee 100644 --- a/include/dialogs/PreviewImageOverlay.h +++ b/include/dialogs/PreviewImageOverlay.h @@ -34,11 +34,11 @@ class PreviewImageOverlay : public QWidget public: PreviewImageOverlay(QWidget *parent = nullptr); - void setImageAndCreate(const QByteArray &data, const QString &type); + void setImageAndCreate(const QByteArray data, const QString &type); void setImageAndCreate(const QString &path); signals: - void confirmImageUpload(const QByteArray &data, const QString &img_name); + void confirmImageUpload(const QByteArray data, const QString &img_name); private: void init(); diff --git a/resources/styles/nheko-dark.qss b/resources/styles/nheko-dark.qss index 9e866992..075df857 100644 --- a/resources/styles/nheko-dark.qss +++ b/resources/styles/nheko-dark.qss @@ -86,12 +86,12 @@ dialogs--CreateRoom, dialogs--InviteUsers, dialogs--ReadReceipts, dialogs--JoinRoom, -dialogs--PreviewImageOverlay, -QListWidget { +dialogs--PreviewImageOverlay { background-color: #383c4a; color: #caccd1; } +QListWidget, WelcomePage, LoginPage, RegisterPage { diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc index 53d725c6..aee2f8e5 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc @@ -724,7 +724,7 @@ MatrixClient::uploadFile(const QString &roomid, { auto reply = makeUploadRequest(data); - connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename]() { + connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, data, filename]() { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -764,7 +764,7 @@ MatrixClient::uploadAudio(const QString &roomid, { auto reply = makeUploadRequest(data); - connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename]() { + connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, data, filename]() { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -1027,8 +1027,8 @@ MatrixClient::makeUploadRequest(QSharedPointer iodev) endpoint.setPath(mediaApiUrl_ + "/upload"); endpoint.setQuery(query); - if (!iodev->open(QIODevice::ReadWrite)) { - qDebug() << "Error while reading buffer" << iodev.data(); + if (!iodev->open(QIODevice::ReadOnly)) { + qDebug() << "Error while reading buffer" << iodev->errorString(); return nullptr; } @@ -1039,7 +1039,7 @@ MatrixClient::makeUploadRequest(QSharedPointer iodev) request.setHeader(QNetworkRequest::ContentLengthHeader, iodev->size()); request.setHeader(QNetworkRequest::ContentTypeHeader, mime.name()); - auto reply = post(request, iodev->readAll()); + auto reply = post(request, iodev.data()); return reply; } diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc index d1c56c09..02d37b64 100644 --- a/src/TextInputWidget.cc +++ b/src/TextInputWidget.cc @@ -122,27 +122,19 @@ void FilteredTextEdit::insertFromMimeData(const QMimeData *source) { if (source->hasImage()) { - const QImage image = qvariant_cast(source->imageData()); - const auto formats = source->formats(); const auto idx = formats.indexOf( QRegularExpression{"image/\\w+", QRegularExpression::CaseInsensitiveOption}); - // Defaulting to PNG format. + // Note: in the future we may want to look into what the best choice is from the + // formats list. For now we will default to PNG format. QString type = "png"; if (idx != -1) { type = formats.at(idx).split('/')[1]; } // Encode raw pixel data of image. - QByteArray data; - QBuffer buffer(&data); - buffer.open(QIODevice::ReadWrite); - if (!image.save(&buffer, type.toLocal8Bit().data())) { - qWarning() << "Failed to encode image into" << type; - return; - } - + QByteArray data = source->data("image/" + type); previewDialog_.setImageAndCreate(data, type); previewDialog_.show(); } else if (source->hasFormat("x-special/gnome-copied-files") && @@ -227,9 +219,10 @@ FilteredTextEdit::textChanged() } void -FilteredTextEdit::receiveImage(const QByteArray &img, const QString &img_name) +FilteredTextEdit::receiveImage(const QByteArray img, const QString &img_name) { - QSharedPointer buffer{new QBuffer{const_cast(&img), this}}; + QSharedPointer buffer{new QBuffer{this}}; + buffer->setData(img); emit image(buffer, img_name); } diff --git a/src/dialogs/PreviewImageOverlay.cc b/src/dialogs/PreviewImageOverlay.cc index 58317ca4..31ef00ed 100644 --- a/src/dialogs/PreviewImageOverlay.cc +++ b/src/dialogs/PreviewImageOverlay.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -28,9 +29,12 @@ using namespace dialogs; +static constexpr const char *DEFAULT = "Upload image?"; +static constexpr const char *ERROR = "Failed to load image type '%1'. Continue upload?"; + PreviewImageOverlay::PreviewImageOverlay(QWidget *parent) : QWidget{parent} - , titleLabel_{tr("Upload image?"), this} + , titleLabel_{tr(DEFAULT), this} , imageLabel_{this} , imageName_{tr("clipboard"), this} , upload_{tr("Upload"), this} @@ -61,7 +65,7 @@ PreviewImageOverlay::init() auto center = window->frameGeometry().center(); auto img_size = image_.size(); - imageName_.setText(imagePath_); + imageName_.setText(QFileInfo{imagePath_}.fileName()); setAutoFillBackground(true); setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); @@ -93,11 +97,16 @@ PreviewImageOverlay::init() } void -PreviewImageOverlay::setImageAndCreate(const QByteArray &data, const QString &type) +PreviewImageOverlay::setImageAndCreate(const QByteArray data, const QString &type) { - imageData_ = data; - image_.loadFromData(imageData_); - imagePath_ = "clipboard." + type; + imageData_ = data; + imagePath_ = "clipboard." + type; + auto loaded = image_.loadFromData(imageData_); + if (!loaded) { + titleLabel_.setText(QString{tr(ERROR)}.arg(type)); + } else { + titleLabel_.setText(tr(DEFAULT)); + } init(); } @@ -106,16 +115,28 @@ void PreviewImageOverlay::setImageAndCreate(const QString &path) { QFile file{path}; + imagePath_ = path; if (!file.open(QIODevice::ReadOnly)) { - qDebug() << "Failed to read image from:" << path; + qWarning() << "Failed to open image from:" << path; + qWarning() << "Reason:" << file.errorString(); close(); return; } - imageData_ = file.readAll(); - image_.loadFromData(imageData_); - imagePath_ = path; + if ((imageData_ = file.readAll()).isEmpty()) { + qWarning() << "Failed to read image:" << file.errorString(); + close(); + return; + } + + auto loaded = image_.loadFromData(imageData_); + if (!loaded) { + auto t = QFileInfo{path}.suffix(); + titleLabel_.setText(QString{tr(ERROR)}.arg(t)); + } else { + titleLabel_.setText(tr(DEFAULT)); + } init(); } diff --git a/src/timeline/widgets/ImageItem.cc b/src/timeline/widgets/ImageItem.cc index cdccff71..76bb970f 100644 --- a/src/timeline/widgets/ImageItem.cc +++ b/src/timeline/widgets/ImageItem.cc @@ -66,7 +66,7 @@ ImageItem::ImageItem(QSharedPointer client, QWidget *parent) : QWidget(parent) , url_{url} - , text_{QFileInfo{filename}.fileName()} + , text_{filename} , client_{client} { setMouseTracking(true); @@ -84,14 +84,16 @@ ImageItem::ImageItem(QSharedPointer client, url_ = QString("%1/_matrix/media/r0/download/%2") .arg(client_.data()->getHomeServer().toString(), media_params); - if (!data.isNull()) { - data->reset(); - QPixmap p; + auto null = data.isNull(); + auto reset = data->reset(); + QPixmap p; + + if (!null && reset) { p.loadFromData(data->readAll()); - setImage(p); } else { - setImage(QPixmap(filename)); + p.load(filename); } + setImage(p); } void From 59f8b5728ceaf4b0c845353ec4c799dd4ecbad50 Mon Sep 17 00:00:00 2001 From: Chris Tarazi Date: Sun, 7 Jan 2018 13:32:05 -0800 Subject: [PATCH 6/8] Address the issues pointed out in review (3) - Make regex for image type more lenient - Fix inadvertent renaming of parameter --- include/timeline/TimelineView.h | 4 ++-- src/TextInputWidget.cc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/timeline/TimelineView.h b/include/timeline/TimelineView.h index 77eaa0d2..bba40669 100644 --- a/include/timeline/TimelineView.h +++ b/include/timeline/TimelineView.h @@ -50,13 +50,13 @@ struct PendingMessage PendingMessage(mtx::events::MessageType ty, int txn_id, QString body, - const QString &fn, + QString filename, QString event_id, TimelineItem *widget) : ty(ty) , txn_id(txn_id) , body(body) - , filename(fn) + , filename(filename) , event_id(event_id) , widget(widget) {} diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc index 02d37b64..922d72cd 100644 --- a/src/TextInputWidget.cc +++ b/src/TextInputWidget.cc @@ -124,7 +124,7 @@ FilteredTextEdit::insertFromMimeData(const QMimeData *source) if (source->hasImage()) { const auto formats = source->formats(); const auto idx = formats.indexOf( - QRegularExpression{"image/\\w+", QRegularExpression::CaseInsensitiveOption}); + QRegularExpression{"image/.+", QRegularExpression::CaseInsensitiveOption}); // Note: in the future we may want to look into what the best choice is from the // formats list. For now we will default to PNG format. From 9b49b7fdb857386d4f41b29bdf22fcb7ec8b6349 Mon Sep 17 00:00:00 2001 From: Chris Tarazi Date: Mon, 8 Jan 2018 22:38:41 -0800 Subject: [PATCH 7/8] Address the issues pointed out in review (4) - Fix incorrect fallback to filename - Fix incorrect error message - Remove unneeded http content length header - Remove redundant check for MIME data - Run make lint --- include/timeline/TimelineView.h | 3 ++- src/Cache.cc | 3 ++- src/Deserializable.cc | 3 ++- src/Login.cc | 3 ++- src/MatrixClient.cc | 3 +-- src/QuickSwitcher.cc | 3 ++- src/Register.cc | 3 ++- src/TextInputWidget.cc | 3 +-- src/emoji/PickButton.cc | 3 ++- src/timeline/TimelineView.cc | 8 ++------ src/timeline/widgets/ImageItem.cc | 15 +++++++++------ src/ui/Label.cc | 6 ++++-- 12 files changed, 31 insertions(+), 25 deletions(-) diff --git a/include/timeline/TimelineView.h b/include/timeline/TimelineView.h index bba40669..beb8ef35 100644 --- a/include/timeline/TimelineView.h +++ b/include/timeline/TimelineView.h @@ -59,7 +59,8 @@ struct PendingMessage , filename(filename) , event_id(event_id) , widget(widget) - {} + { + } }; // In which place new TimelineItems should be inserted. diff --git a/src/Cache.cc b/src/Cache.cc index e4e700b2..2e0a5a52 100644 --- a/src/Cache.cc +++ b/src/Cache.cc @@ -39,7 +39,8 @@ Cache::Cache(const QString &userId) , readReceiptsDb_{0} , isMounted_{false} , userId_{userId} -{} +{ +} void Cache::setup() diff --git a/src/Deserializable.cc b/src/Deserializable.cc index 8bdbfc2c..e62f9d43 100644 --- a/src/Deserializable.cc +++ b/src/Deserializable.cc @@ -19,7 +19,8 @@ DeserializationException::DeserializationException(const std::string &msg) : msg_(msg) -{} +{ +} const char * DeserializationException::what() const noexcept diff --git a/src/Login.cc b/src/Login.cc index 3e681a76..b5669c26 100644 --- a/src/Login.cc +++ b/src/Login.cc @@ -23,7 +23,8 @@ LoginRequest::LoginRequest() {} LoginRequest::LoginRequest(QString username, QString password) : user_(username) , password_(password) -{} +{ +} QByteArray LoginRequest::serialize() noexcept diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc index aee2f8e5..a5ccaed1 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc @@ -1028,7 +1028,7 @@ MatrixClient::makeUploadRequest(QSharedPointer iodev) endpoint.setQuery(query); if (!iodev->open(QIODevice::ReadOnly)) { - qDebug() << "Error while reading buffer" << iodev->errorString(); + qWarning() << "Error while reading device:" << iodev->errorString(); return nullptr; } @@ -1036,7 +1036,6 @@ MatrixClient::makeUploadRequest(QSharedPointer iodev) QMimeType mime = db.mimeTypeForData(iodev.data()); QNetworkRequest request(QString(endpoint.toEncoded())); - request.setHeader(QNetworkRequest::ContentLengthHeader, iodev->size()); request.setHeader(QNetworkRequest::ContentTypeHeader, mime.name()); auto reply = post(request, iodev.data()); diff --git a/src/QuickSwitcher.cc b/src/QuickSwitcher.cc index 2be636a4..13c63a04 100644 --- a/src/QuickSwitcher.cc +++ b/src/QuickSwitcher.cc @@ -23,7 +23,8 @@ RoomSearchInput::RoomSearchInput(QWidget *parent) : TextField(parent) -{} +{ +} bool RoomSearchInput::focusNextPrevChild(bool next) diff --git a/src/Register.cc b/src/Register.cc index 84ba82e7..d00ce9a8 100644 --- a/src/Register.cc +++ b/src/Register.cc @@ -22,7 +22,8 @@ RegisterRequest::RegisterRequest(const QString &username, const QString &password) : user_(username) , password_(password) -{} +{ +} QByteArray RegisterRequest::serialize() noexcept diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc index 922d72cd..f9198c78 100644 --- a/src/TextInputWidget.cc +++ b/src/TextInputWidget.cc @@ -114,8 +114,7 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event) bool FilteredTextEdit::canInsertFromMimeData(const QMimeData *source) const { - return (source->hasImage() || source->hasText() || - QTextEdit::canInsertFromMimeData(source)); + return (source->hasImage() || QTextEdit::canInsertFromMimeData(source)); } void diff --git a/src/emoji/PickButton.cc b/src/emoji/PickButton.cc index 530c951b..c1731f4c 100644 --- a/src/emoji/PickButton.cc +++ b/src/emoji/PickButton.cc @@ -23,7 +23,8 @@ using namespace emoji; PickButton::PickButton(QWidget *parent) : FlatButton(parent) , panel_{nullptr} -{} +{ +} void PickButton::enterEvent(QEvent *e) diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc index 29929cdd..75ce8141 100644 --- a/src/timeline/TimelineView.cc +++ b/src/timeline/TimelineView.cc @@ -510,12 +510,8 @@ TimelineView::sendNextPendingMessage() case mtx::events::MessageType::Image: case mtx::events::MessageType::File: // FIXME: Improve the API - client_->sendRoomMessage(m.ty, - m.txn_id, - room_id_, - m.filename, - QFileInfo(m.filename), - m.body); + client_->sendRoomMessage( + m.ty, m.txn_id, room_id_, m.filename, QFileInfo(m.filename), m.body); break; default: client_->sendRoomMessage(m.ty, m.txn_id, room_id_, m.body, QFileInfo()); diff --git a/src/timeline/widgets/ImageItem.cc b/src/timeline/widgets/ImageItem.cc index 76bb970f..48a4c1eb 100644 --- a/src/timeline/widgets/ImageItem.cc +++ b/src/timeline/widgets/ImageItem.cc @@ -84,16 +84,19 @@ ImageItem::ImageItem(QSharedPointer client, url_ = QString("%1/_matrix/media/r0/download/%2") .arg(client_.data()->getHomeServer().toString(), media_params); - auto null = data.isNull(); - auto reset = data->reset(); - QPixmap p; + if (data.isNull()) { + qWarning() << "No image data to display"; + return; + } - if (!null && reset) { + if (data->reset()) { + QPixmap p; p.loadFromData(data->readAll()); + setImage(p); } else { - p.load(filename); + qWarning() << "Failed to seek to beginning of device:" << data->errorString(); + return; } - setImage(p); } void diff --git a/src/ui/Label.cc b/src/ui/Label.cc index 8bd8c54e..756441ef 100644 --- a/src/ui/Label.cc +++ b/src/ui/Label.cc @@ -20,11 +20,13 @@ Label::Label(QWidget *parent, Qt::WindowFlags f) : QLabel(parent, f) -{} +{ +} Label::Label(const QString &text, QWidget *parent, Qt::WindowFlags f) : QLabel(text, parent, f) -{} +{ +} void Label::mousePressEvent(QMouseEvent *e) From f503e1f4a3920b09cea13c5d75b3e7ea651fd766 Mon Sep 17 00:00:00 2001 From: Chris Tarazi Date: Tue, 9 Jan 2018 12:12:34 -0800 Subject: [PATCH 8/8] Revert unnecessary changes from make lint --- include/timeline/TimelineView.h | 3 +-- src/Cache.cc | 3 +-- src/Deserializable.cc | 3 +-- src/Login.cc | 3 +-- src/QuickSwitcher.cc | 3 +-- src/Register.cc | 3 +-- src/emoji/PickButton.cc | 3 +-- src/ui/Label.cc | 6 ++---- 8 files changed, 9 insertions(+), 18 deletions(-) diff --git a/include/timeline/TimelineView.h b/include/timeline/TimelineView.h index beb8ef35..bba40669 100644 --- a/include/timeline/TimelineView.h +++ b/include/timeline/TimelineView.h @@ -59,8 +59,7 @@ struct PendingMessage , filename(filename) , event_id(event_id) , widget(widget) - { - } + {} }; // In which place new TimelineItems should be inserted. diff --git a/src/Cache.cc b/src/Cache.cc index 2e0a5a52..e4e700b2 100644 --- a/src/Cache.cc +++ b/src/Cache.cc @@ -39,8 +39,7 @@ Cache::Cache(const QString &userId) , readReceiptsDb_{0} , isMounted_{false} , userId_{userId} -{ -} +{} void Cache::setup() diff --git a/src/Deserializable.cc b/src/Deserializable.cc index e62f9d43..8bdbfc2c 100644 --- a/src/Deserializable.cc +++ b/src/Deserializable.cc @@ -19,8 +19,7 @@ DeserializationException::DeserializationException(const std::string &msg) : msg_(msg) -{ -} +{} const char * DeserializationException::what() const noexcept diff --git a/src/Login.cc b/src/Login.cc index b5669c26..3e681a76 100644 --- a/src/Login.cc +++ b/src/Login.cc @@ -23,8 +23,7 @@ LoginRequest::LoginRequest() {} LoginRequest::LoginRequest(QString username, QString password) : user_(username) , password_(password) -{ -} +{} QByteArray LoginRequest::serialize() noexcept diff --git a/src/QuickSwitcher.cc b/src/QuickSwitcher.cc index 13c63a04..2be636a4 100644 --- a/src/QuickSwitcher.cc +++ b/src/QuickSwitcher.cc @@ -23,8 +23,7 @@ RoomSearchInput::RoomSearchInput(QWidget *parent) : TextField(parent) -{ -} +{} bool RoomSearchInput::focusNextPrevChild(bool next) diff --git a/src/Register.cc b/src/Register.cc index d00ce9a8..84ba82e7 100644 --- a/src/Register.cc +++ b/src/Register.cc @@ -22,8 +22,7 @@ RegisterRequest::RegisterRequest(const QString &username, const QString &password) : user_(username) , password_(password) -{ -} +{} QByteArray RegisterRequest::serialize() noexcept diff --git a/src/emoji/PickButton.cc b/src/emoji/PickButton.cc index c1731f4c..530c951b 100644 --- a/src/emoji/PickButton.cc +++ b/src/emoji/PickButton.cc @@ -23,8 +23,7 @@ using namespace emoji; PickButton::PickButton(QWidget *parent) : FlatButton(parent) , panel_{nullptr} -{ -} +{} void PickButton::enterEvent(QEvent *e) diff --git a/src/ui/Label.cc b/src/ui/Label.cc index 756441ef..8bd8c54e 100644 --- a/src/ui/Label.cc +++ b/src/ui/Label.cc @@ -20,13 +20,11 @@ Label::Label(QWidget *parent, Qt::WindowFlags f) : QLabel(parent, f) -{ -} +{} Label::Label(const QString &text, QWidget *parent, Qt::WindowFlags f) : QLabel(text, parent, f) -{ -} +{} void Label::mousePressEvent(QMouseEvent *e)