diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | resources/qml/TimelineView.qml | 13 | ||||
-rw-r--r-- | resources/qml/delegates/ImageMessage.qml | 2 | ||||
-rw-r--r-- | src/MatrixClient.h | 10 | ||||
-rw-r--r-- | src/timeline2/TimelineModel.cpp | 55 | ||||
-rw-r--r-- | src/timeline2/TimelineModel.h | 2 | ||||
-rw-r--r-- | src/timeline2/TimelineViewManager.cpp | 98 | ||||
-rw-r--r-- | src/timeline2/TimelineViewManager.h | 25 |
9 files changed, 174 insertions, 33 deletions
diff --git a/.gitignore b/.gitignore index 0f61a911..2d772e58 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,7 @@ ui_*.h # Vim *.swp +*.swo #####=== CMake ===##### diff --git a/CMakeLists.txt b/CMakeLists.txt index d386efbf..1cf34c32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -375,7 +375,6 @@ qt5_wrap_cpp(MOC_HEADERS src/CommunitiesList.h src/LoginPage.h src/MainWindow.h - src/MatrixClient.h src/InviteeItem.h src/QuickSwitcher.h src/RegisterPage.h diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index ee4b53b9..051ea915 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -187,18 +187,23 @@ Rectangle { id: contextMenu MenuItem { - text: "Read receipts" + text: qsTr("Read receipts") onTriggered: chat.model.readReceiptsAction(model.id) } MenuItem { - text: "Mark as read" + text: qsTr("Mark as read") } MenuItem { - text: "View raw message" + text: qsTr("View raw message") onTriggered: chat.model.viewRawMessage(model.id) } MenuItem { - text: "Redact message" + text: qsTr("Redact message") + } + MenuItem { + visible: model.type == MtxEvent.ImageMessage || model.type == MtxEvent.VideoMessage || model.type == MtxEvent.AudioMessage || model.type == MtxEvent.FileMessage + text: qsTr("Save as") + onTriggered: timelineManager.saveMedia(model.url, model.filename, model.mimetype, model.type) } } } diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index f4f5e369..3f5c00bf 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -14,7 +14,7 @@ Item { MouseArea { anchors.fill: parent - onClicked: timelineManager.openImageOverlay(img.source) + onClicked: timelineManager.openImageOverlay(eventData.url, eventData.filename, eventData.mimetype, eventData.type) } } } diff --git a/src/MatrixClient.h b/src/MatrixClient.h index 2af57267..c77b1183 100644 --- a/src/MatrixClient.h +++ b/src/MatrixClient.h @@ -20,16 +20,6 @@ Q_DECLARE_METATYPE(nlohmann::json) Q_DECLARE_METATYPE(std::vector<std::string>) Q_DECLARE_METATYPE(std::vector<QString>) -class MediaProxy : public QObject -{ - Q_OBJECT - -signals: - void imageDownloaded(const QPixmap &); - void imageSaved(const QString &, const QByteArray &); - void fileDownloaded(const QByteArray &); -}; - namespace http { mtx::http::Client * client(); diff --git a/src/timeline2/TimelineModel.cpp b/src/timeline2/TimelineModel.cpp index 36b768ba..b702686e 100644 --- a/src/timeline2/TimelineModel.cpp +++ b/src/timeline2/TimelineModel.cpp @@ -105,6 +105,53 @@ eventUrl(const mtx::events::RoomEvent<T> &e) } template<class T> +QString +eventFilename(const T &) +{ + return ""; +} +QString +eventFilename(const mtx::events::RoomEvent<mtx::events::msg::Audio> &e) +{ + // body may be the original filename + return QString::fromStdString(e.content.body); +} +QString +eventFilename(const mtx::events::RoomEvent<mtx::events::msg::Video> &e) +{ + // body may be the original filename + return QString::fromStdString(e.content.body); +} +QString +eventFilename(const mtx::events::RoomEvent<mtx::events::msg::Image> &e) +{ + // body may be the original filename + return QString::fromStdString(e.content.body); +} +QString +eventFilename(const mtx::events::RoomEvent<mtx::events::msg::File> &e) +{ + // body may be the original filename + if (!e.content.filename.empty()) + return QString::fromStdString(e.content.filename); + return QString::fromStdString(e.content.body); +} + +template<class T> +QString +eventMimeType(const T &) +{ + return QString(); +} +template<class T> +auto +eventMimeType(const mtx::events::RoomEvent<T> &e) + -> std::enable_if_t<std::is_same<decltype(e.content.info.mimetype), std::string>::value, QString> +{ + return QString::fromStdString(e.content.info.mimetype); +} + +template<class T> qml_mtx_events::EventType toRoomEventType(const mtx::events::Event<T> &e) { @@ -288,6 +335,8 @@ TimelineModel::roleNames() const {UserName, "userName"}, {Timestamp, "timestamp"}, {Url, "url"}, + {Filename, "filename"}, + {MimeType, "mimetype"}, {Height, "height"}, {Width, "width"}, {ProportionalHeight, "proportionalHeight"}, @@ -366,6 +415,12 @@ TimelineModel::data(const QModelIndex &index, int role) const case Url: return QVariant(boost::apply_visitor( [](const auto &e) -> QString { return eventUrl(e); }, event)); + case Filename: + return QVariant(boost::apply_visitor( + [](const auto &e) -> QString { return eventFilename(e); }, event)); + case MimeType: + return QVariant(boost::apply_visitor( + [](const auto &e) -> QString { return eventMimeType(e); }, event)); case Height: return QVariant(boost::apply_visitor( [](const auto &e) -> qulonglong { return eventHeight(e); }, event)); diff --git a/src/timeline2/TimelineModel.h b/src/timeline2/TimelineModel.h index 3d55f206..e10a0b6e 100644 --- a/src/timeline2/TimelineModel.h +++ b/src/timeline2/TimelineModel.h @@ -127,6 +127,8 @@ public: UserName, Timestamp, Url, + Filename, + MimeType, Height, Width, ProportionalHeight, diff --git a/src/timeline2/TimelineViewManager.cpp b/src/timeline2/TimelineViewManager.cpp index bf09ef5a..eed0682d 100644 --- a/src/timeline2/TimelineViewManager.cpp +++ b/src/timeline2/TimelineViewManager.cpp @@ -1,6 +1,8 @@ #include "TimelineViewManager.h" +#include <QFileDialog> #include <QMetaType> +#include <QMimeDatabase> #include <QQmlContext> #include "Logging.h" @@ -55,24 +57,88 @@ TimelineViewManager::setHistoryView(const QString &room_id) } void -TimelineViewManager::openImageOverlay(QString url) const +TimelineViewManager::openImageOverlay(QString mxcUrl, + QString originalFilename, + QString mimeType, + qml_mtx_events::EventType eventType) const { QQuickImageResponse *imgResponse = - imgProvider->requestImageResponse(url.remove("image://mxcimage/"), QSize()); - connect(imgResponse, &QQuickImageResponse::finished, this, [imgResponse]() { - if (!imgResponse->errorString().isEmpty()) { - nhlog::ui()->error("Error when retrieving image for overlay: {}", - imgResponse->errorString().toStdString()); - return; - } - auto pixmap = QPixmap::fromImage(imgResponse->textureFactory()->image()); - - auto imgDialog = new dialogs::ImageOverlay(pixmap); - imgDialog->show(); - // connect(imgDialog, &dialogs::ImageOverlay::saving, this, - // &ImageItem::saveAs); - Q_UNUSED(imgDialog); - }); + imgProvider->requestImageResponse(mxcUrl.remove("mxc://"), QSize()); + connect(imgResponse, + &QQuickImageResponse::finished, + this, + [this, mxcUrl, originalFilename, mimeType, eventType, imgResponse]() { + if (!imgResponse->errorString().isEmpty()) { + nhlog::ui()->error("Error when retrieving image for overlay: {}", + imgResponse->errorString().toStdString()); + return; + } + auto pixmap = QPixmap::fromImage(imgResponse->textureFactory()->image()); + + auto imgDialog = new dialogs::ImageOverlay(pixmap); + imgDialog->show(); + connect(imgDialog, + &dialogs::ImageOverlay::saving, + this, + [this, mxcUrl, originalFilename, mimeType, eventType]() { + saveMedia(mxcUrl, originalFilename, mimeType, eventType); + }); + }); +} + +void +TimelineViewManager::saveMedia(QString mxcUrl, + QString originalFilename, + QString mimeType, + qml_mtx_events::EventType eventType) const +{ + QString dialogTitle; + if (eventType == qml_mtx_events::EventType::ImageMessage) { + dialogTitle = tr("Save image"); + } else if (eventType == qml_mtx_events::EventType::VideoMessage) { + dialogTitle = tr("Save video"); + } else if (eventType == qml_mtx_events::EventType::AudioMessage) { + dialogTitle = tr("Save audio"); + } else { + dialogTitle = tr("Save file"); + } + + QString filterString = QMimeDatabase().mimeTypeForName(mimeType).filterString(); + + auto filename = + QFileDialog::getSaveFileName(container, dialogTitle, originalFilename, filterString); + + if (filename.isEmpty()) + return; + + const auto url = mxcUrl.toStdString(); + + http::client()->download( + url, + [filename, url](const std::string &data, + const std::string &, + const std::string &, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve image {}: {} {}", + url, + err->matrix_error.error, + static_cast<int>(err->status_code)); + return; + } + + try { + QFile file(filename); + + if (!file.open(QIODevice::WriteOnly)) + return; + + file.write(QByteArray(data.data(), data.size())); + file.close(); + } catch (const std::exception &e) { + nhlog::ui()->warn("Error while saving file to: {}", e.what()); + } + }); } void diff --git a/src/timeline2/TimelineViewManager.h b/src/timeline2/TimelineViewManager.h index 68f6ddb0..687ae24e 100644 --- a/src/timeline2/TimelineViewManager.h +++ b/src/timeline2/TimelineViewManager.h @@ -35,7 +35,30 @@ public: void clearAll() { models.clear(); } Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; } - Q_INVOKABLE void openImageOverlay(QString url) const; + void openImageOverlay(QString mxcUrl, + QString originalFilename, + QString mimeType, + qml_mtx_events::EventType eventType) const; + void saveMedia(QString mxcUrl, + QString originalFilename, + QString mimeType, + qml_mtx_events::EventType eventType) const; + // Qml can only pass enum as int + Q_INVOKABLE void openImageOverlay(QString mxcUrl, + QString originalFilename, + QString mimeType, + int eventType) const + { + openImageOverlay( + mxcUrl, originalFilename, mimeType, (qml_mtx_events::EventType)eventType); + } + Q_INVOKABLE void saveMedia(QString mxcUrl, + QString originalFilename, + QString mimeType, + int eventType) const + { + saveMedia(mxcUrl, originalFilename, mimeType, (qml_mtx_events::EventType)eventType); + } signals: void clearRoomMessageCount(QString roomid); |