From ddfce136ed4de5b80ae5961b6a11363624e0b244 Mon Sep 17 00:00:00 2001 From: christarazi Date: Tue, 9 Jan 2018 23:52:59 -0800 Subject: [PATCH] Add support for pasting images into a room (#180) fixes #132 --- CMakeLists.txt | 2 + include/MatrixClient.h | 19 +++- include/TextInputWidget.h | 18 +++- include/dialogs/PreviewImageOverlay.h | 57 ++++++++++ include/timeline/TimelineView.h | 10 +- include/timeline/TimelineViewManager.h | 7 +- include/timeline/widgets/AudioItem.h | 1 + include/timeline/widgets/FileItem.h | 1 + include/timeline/widgets/ImageItem.h | 1 + include/timeline/widgets/VideoItem.h | 1 + resources/styles/nheko-dark.qss | 9 +- resources/styles/nheko.qss | 7 +- src/ChatPage.cc | 31 ++++-- src/MatrixClient.cc | 44 ++++---- src/TextInputWidget.cc | 65 ++++++++++- src/dialogs/PreviewImageOverlay.cc | 142 +++++++++++++++++++++++++ src/timeline/TimelineView.cc | 8 +- src/timeline/TimelineViewManager.cc | 7 +- src/timeline/widgets/AudioItem.cc | 6 +- src/timeline/widgets/FileItem.cc | 6 +- src/timeline/widgets/ImageItem.cc | 17 ++- src/timeline/widgets/VideoItem.cc | 2 + 22 files changed, 388 insertions(+), 73 deletions(-) create mode 100644 include/dialogs/PreviewImageOverlay.h create mode 100644 src/dialogs/PreviewImageOverlay.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index f44198f2..876409b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,6 +144,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 @@ -229,6 +230,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/MatrixClient.h b/include/MatrixClient.h index 8936003f..df21ccdb 100644 --- a/include/MatrixClient.h +++ b/include/MatrixClient.h @@ -55,9 +55,15 @@ public: 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, + 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); @@ -98,7 +104,10 @@ signals: const QString &homeserver, const QString &token); void versionSuccess(); - void imageUploaded(const QString &roomid, const QString &filename, 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); @@ -131,7 +140,7 @@ signals: 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 df309e27..cc01be69 100644 --- a/include/TextInputWidget.h +++ b/include/TextInputWidget.h @@ -27,8 +27,14 @@ #include "FlatButton.h" #include "LoadingIndicator.h" +#include "dialogs/PreviewImageOverlay.h" + #include "emoji/PickButton.h" +namespace dialogs { +class PreviewImageOverlay; +} + class FilteredTextEdit : public QTextEdit { Q_OBJECT @@ -48,16 +54,22 @@ signals: void stoppedTyping(); void message(QString); void command(QString name, QString args); + void image(const QSharedPointer iodev, const QString &img_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 QByteArray img, const QString &img_name); void afterCompletion(int); }; @@ -83,9 +95,9 @@ signals: void sendTextMessage(QString msg); void sendEmoteMessage(QString msg); - void uploadImage(QString filename); - void uploadFile(QString filename); - void uploadAudio(QString filename); + 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 new file mode 100644 index 00000000..a1ab32ee --- /dev/null +++ b/include/dialogs/PreviewImageOverlay.h @@ -0,0 +1,57 @@ +/* + * 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 +#include +#include + +#include "FlatButton.h" + +class QMimeData; + +namespace dialogs { + +class PreviewImageOverlay : public QWidget +{ + Q_OBJECT +public: + PreviewImageOverlay(QWidget *parent = nullptr); + + void setImageAndCreate(const QByteArray data, const QString &type); + void setImageAndCreate(const QString &path); + +signals: + void confirmImageUpload(const QByteArray data, const QString &img_name); + +private: + void init(); + + QPixmap image_; + QByteArray imageData_; + QString imagePath_; + + QLabel titleLabel_; + QLabel imageLabel_; + QLineEdit imageName_; + + FlatButton upload_; + FlatButton cancel_; +}; +} // dialogs diff --git a/include/timeline/TimelineView.h b/include/timeline/TimelineView.h index cde64148..bba40669 100644 --- a/include/timeline/TimelineView.h +++ b/include/timeline/TimelineView.h @@ -87,7 +87,9 @@ public: void addUserMessage(mtx::events::MessageType ty, const QString &msg); template - void addUserMessage(const QString &url, const QString &filename); + 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: template void -TimelineView::addUserMessage(const QString &url, const QString &filename) +TimelineView::addUserMessage(const QString &url, + const QSharedPointer data, + const QString &filename) { auto with_sender = lastSender_ != local_user_; - auto widget = new Widget(client_, url, filename, this); + auto widget = new Widget(client_, url, data, filename, this); TimelineItem *view_item = new TimelineItem(widget, local_user_, with_sender, scroll_widget_); diff --git a/include/timeline/TimelineViewManager.h b/include/timeline/TimelineViewManager.h index 2c32da16..c19031c6 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,7 +66,10 @@ 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 queueImageMessage(const QString &roomid, + 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); diff --git a/include/timeline/widgets/AudioItem.h b/include/timeline/widgets/AudioItem.h index f8e7cc07..ca81f498 100644 --- a/include/timeline/widgets/AudioItem.h +++ b/include/timeline/widgets/AudioItem.h @@ -48,6 +48,7 @@ public: AudioItem(QSharedPointer client, const QString &url, + const QSharedPointer data, const QString &filename, QWidget *parent = nullptr); diff --git a/include/timeline/widgets/FileItem.h b/include/timeline/widgets/FileItem.h index fd0b0249..72589189 100644 --- a/include/timeline/widgets/FileItem.h +++ b/include/timeline/widgets/FileItem.h @@ -42,6 +42,7 @@ public: FileItem(QSharedPointer client, const QString &url, + const QSharedPointer data, const QString &filename, QWidget *parent = nullptr); diff --git a/include/timeline/widgets/ImageItem.h b/include/timeline/widgets/ImageItem.h index 467c70ab..d24b7239 100644 --- a/include/timeline/widgets/ImageItem.h +++ b/include/timeline/widgets/ImageItem.h @@ -36,6 +36,7 @@ public: ImageItem(QSharedPointer client, const QString &url, + const QSharedPointer data, const QString &filename, QWidget *parent = nullptr); 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 @@ public: VideoItem(QSharedPointer client, const QString &url, + const QSharedPointer data, const QString &filename, QWidget *parent = nullptr); diff --git a/resources/styles/nheko-dark.qss b/resources/styles/nheko-dark.qss index 26425590..88631858 100644 --- a/resources/styles/nheko-dark.qss +++ b/resources/styles/nheko-dark.qss @@ -95,16 +95,13 @@ dialogs--LeaveRoom, dialogs--CreateRoom, dialogs--InviteUsers, dialogs--ReadReceipts, -dialogs--JoinRoom { - background-color: #383c4a; - color: #caccd1; -} - -QListWidget { +dialogs--JoinRoom, +dialogs--PreviewImageOverlay { background-color: #383c4a; color: #caccd1; } +QListWidget, WelcomePage, LoginPage, RegisterPage { diff --git a/resources/styles/nheko.qss b/resources/styles/nheko.qss index c135c12a..a5f99353 100644 --- a/resources/styles/nheko.qss +++ b/resources/styles/nheko.qss @@ -98,11 +98,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/ChatPage.cc b/src/ChatPage.cc index ebdec835..f49c0a08 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -228,17 +228,26 @@ 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 data, const QString &fn) { + client_->uploadImage(current_room_, data, fn); + }); - connect(text_input_, &TextInputWidget::uploadFile, this, [=](QString filename) { - client_->uploadFile(current_room_, filename); - }); + connect(text_input_, + &TextInputWidget::uploadFile, + this, + [=](QSharedPointer data, const QString &fn) { + client_->uploadFile(current_room_, data, fn); + }); - connect(text_input_, &TextInputWidget::uploadAudio, this, [=](QString filename) { - client_->uploadAudio(current_room_, filename); - }); + 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); @@ -246,9 +255,9 @@ ChatPage::ChatPage(QSharedPointer client, connect(client_.data(), &MatrixClient::imageUploaded, this, - [=](QString roomid, QString filename, QString url) { + [=](QString roomid, QSharedPointer data, QString filename, QString url) { text_input_->hideUploadSpinner(); - view_manager_->queueImageMessage(roomid, filename, url); + view_manager_->queueImageMessage(roomid, data, filename, url); }); connect(client_.data(), &MatrixClient::fileUploaded, diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc index 72467385..67203d59 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc @@ -821,14 +821,16 @@ 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, + const QSharedPointer data, + const QString &filename) { - auto reply = makeUploadRequest(filename); + auto reply = makeUploadRequest(data); if (reply == nullptr) return; - 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(); @@ -838,12 +840,12 @@ MatrixClient::uploadImage(const QString &roomid, const QString &filename) 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."; @@ -857,16 +859,18 @@ MatrixClient::uploadImage(const QString &roomid, const QString &filename) return; } - emit imageUploaded(roomid, filename, object.value("content_uri").toString()); + emit imageUploaded(roomid, data, filename, object.value("content_uri").toString()); }); } void -MatrixClient::uploadFile(const QString &roomid, const QString &filename) +MatrixClient::uploadFile(const QString &roomid, + const QSharedPointer data, + const QString &filename) { - auto reply = makeUploadRequest(filename); + 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(); @@ -900,11 +904,13 @@ MatrixClient::uploadFile(const QString &roomid, const QString &filename) } void -MatrixClient::uploadAudio(const QString &roomid, const QString &filename) +MatrixClient::uploadAudio(const QString &roomid, + const QSharedPointer data, + const QString &filename) { - auto reply = makeUploadRequest(filename); + 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(); @@ -1158,7 +1164,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_); @@ -1167,20 +1173,18 @@ 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::ReadOnly)) { + qWarning() << "Error while reading device:" << iodev->errorString(); 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::ContentTypeHeader, mime.name()); - auto reply = post(request, file.readAll()); + auto reply = post(request, iodev.data()); return reply; } diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc index dc2bebe7..f9198c78 100644 --- a/src/TextInputWidget.cc +++ b/src/TextInputWidget.cc @@ -16,10 +16,13 @@ */ #include +#include +#include +#include #include -#include #include #include +#include #include #include #include @@ -33,6 +36,7 @@ static constexpr size_t INPUT_HISTORY_SIZE = 127; FilteredTextEdit::FilteredTextEdit(QWidget *parent) : QTextEdit{parent} , history_index_{0} + , previewDialog_{parent} { connect(document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, @@ -50,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 @@ -101,6 +111,42 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event) } } +bool +FilteredTextEdit::canInsertFromMimeData(const QMimeData *source) const +{ + return (source->hasImage() || QTextEdit::canInsertFromMimeData(source)); +} + +void +FilteredTextEdit::insertFromMimeData(const QMimeData *source) +{ + if (source->hasImage()) { + const auto formats = source->formats(); + const auto idx = formats.indexOf( + 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. + QString type = "png"; + if (idx != -1) { + type = formats.at(idx).split('/')[1]; + } + + // Encode raw pixel data of image. + QByteArray data = source->data("image/" + type); + 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_.setImageAndCreate(source->text()); + previewDialog_.show(); + } else { + QTextEdit::insertFromMimeData(source); + } +} + void FilteredTextEdit::stopTyping() { @@ -146,6 +192,7 @@ FilteredTextEdit::submit() history_index_ = 0; QString text = toPlainText(); + if (text.startsWith('/')) { int command_end = text.indexOf(' '); if (command_end == -1) @@ -170,6 +217,14 @@ FilteredTextEdit::textChanged() working_history_[history_index_] = toPlainText(); } +void +FilteredTextEdit::receiveImage(const QByteArray img, const QString &img_name) +{ + QSharedPointer buffer{new QBuffer{this}}; + buffer->setData(img); + emit image(buffer, img_name); +} + TextInputWidget::TextInputWidget(QWidget *parent) : QFrame(parent) { @@ -231,6 +286,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, @@ -289,12 +345,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, fileName); else if (format == "audio") - emit uploadAudio(fileName); + emit uploadAudio(file, fileName); else - emit uploadFile(fileName); + emit uploadFile(file, fileName); showUploadSpinner(); } diff --git a/src/dialogs/PreviewImageOverlay.cc b/src/dialogs/PreviewImageOverlay.cc new file mode 100644 index 00000000..31ef00ed --- /dev/null +++ b/src/dialogs/PreviewImageOverlay.cc @@ -0,0 +1,142 @@ +/* + * 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 "Config.h" + +#include "dialogs/PreviewImageOverlay.h" + +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(DEFAULT), this} + , imageLabel_{this} + , imageName_{tr("clipboard"), this} + , upload_{tr("Upload"), this} + , cancel_{tr("Cancel"), this} +{ + 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(imageData_, imageName_.text()); + close(); + }); + connect(&cancel_, &QPushButton::clicked, [&]() { close(); }); +} + +void +PreviewImageOverlay::init() +{ + auto window = QApplication::activeWindow(); + auto winsize = window->frameGeometry().size(); + auto center = window->frameGeometry().center(); + auto img_size = image_.size(); + + imageName_.setText(QFileInfo{imagePath_}.fileName()); + + setAutoFillBackground(true); + setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); + setWindowModality(Qt::WindowModal); + + 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)); + } else { + imageLabel_.setPixmap(image_); + move(center.x() - (width() * 0.5), center.y() - (height() * 0.5)); + } + imageLabel_.setScaledContents(false); + + raise(); +} + +void +PreviewImageOverlay::setImageAndCreate(const QByteArray data, const QString &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(); +} + +void +PreviewImageOverlay::setImageAndCreate(const QString &path) +{ + QFile file{path}; + imagePath_ = path; + + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << "Failed to open image from:" << path; + qWarning() << "Reason:" << file.errorString(); + close(); + return; + } + + 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/TimelineView.cc b/src/timeline/TimelineView.cc index d21f30f0..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_, - QFileInfo(m.filename).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/TimelineViewManager.cc b/src/timeline/TimelineViewManager.cc index de1e1e32..65c9ac83 100644 --- a/src/timeline/TimelineViewManager.cc +++ b/src/timeline/TimelineViewManager.cc @@ -85,6 +85,7 @@ TimelineViewManager::queueEmoteMessage(const QString &msg) void TimelineViewManager::queueImageMessage(const QString &roomid, + const QSharedPointer data, const QString &filename, const QString &url) { @@ -95,7 +96,7 @@ TimelineViewManager::queueImageMessage(const QString &roomid, auto view = views_[roomid]; - view->addUserMessage(url, filename); + view->addUserMessage(url, data, filename); } void @@ -110,7 +111,7 @@ TimelineViewManager::queueFileMessage(const QString &roomid, auto view = views_[roomid]; - view->addUserMessage(url, filename); + view->addUserMessage(url, nullptr, filename); } void @@ -125,7 +126,7 @@ TimelineViewManager::queueAudioMessage(const QString &roomid, auto view = views_[roomid]; - view->addUserMessage(url, filename); + view->addUserMessage(url, nullptr, filename); } void diff --git a/src/timeline/widgets/AudioItem.cc b/src/timeline/widgets/AudioItem.cc index 5d9dd77b..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, + const QSharedPointer data, const QString &filename, QWidget *parent) : QWidget(parent) , url_{url} - , text_{QFileInfo(filename).fileName()} + , text_{QFileInfo{filename}.fileName()} , client_{client} { - readableFileSize_ = calculateFileSize(QFileInfo(filename).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 3c38dc31..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, + const QSharedPointer data, const QString &filename, QWidget *parent) : QWidget(parent) , url_{url} - , text_{QFileInfo(filename).fileName()} + , text_{QFileInfo{filename}.fileName()} , client_{client} { - readableFileSize_ = calculateFileSize(QFileInfo(filename).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 9038456d..48a4c1eb 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, + const QSharedPointer data, const QString &filename, QWidget *parent) : QWidget(parent) , url_{url} - , text_{QFileInfo(filename).fileName()} + , text_{filename} , client_{client} { setMouseTracking(true); @@ -83,7 +84,19 @@ ImageItem::ImageItem(QSharedPointer client, url_ = QString("%1/_matrix/media/r0/download/%2") .arg(client_.data()->getHomeServer().toString(), media_params); - setImage(QPixmap(filename)); + if (data.isNull()) { + qWarning() << "No image data to display"; + return; + } + + if (data->reset()) { + QPixmap p; + p.loadFromData(data->readAll()); + setImage(p); + } else { + qWarning() << "Failed to seek to beginning of device:" << data->errorString(); + return; + } } 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();