diff options
-rw-r--r-- | resources/icons/ui/copy.svg | 1 | ||||
-rw-r--r-- | resources/qml/dialogs/ImageOverlay.qml | 36 | ||||
-rw-r--r-- | resources/res.qrc | 1 | ||||
-rw-r--r-- | src/timeline/InputBar.cpp | 1 | ||||
-rw-r--r-- | src/timeline/TimelineModel.cpp | 55 | ||||
-rw-r--r-- | src/timeline/TimelineModel.h | 1 | ||||
-rw-r--r-- | src/timeline/TimelineViewManager.cpp | 35 | ||||
-rw-r--r-- | src/timeline/TimelineViewManager.h | 1 |
8 files changed, 129 insertions, 2 deletions
diff --git a/resources/icons/ui/copy.svg b/resources/icons/ui/copy.svg new file mode 100644 index 00000000..ae358603 --- /dev/null +++ b/resources/icons/ui/copy.svg @@ -0,0 +1 @@ +<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M5.503 4.627 5.5 6.75v10.504a3.25 3.25 0 0 0 3.25 3.25h8.616a2.251 2.251 0 0 1-2.122 1.5H8.75A4.75 4.75 0 0 1 4 17.254V6.75c0-.98.627-1.815 1.503-2.123ZM17.75 2A2.25 2.25 0 0 1 20 4.25v13a2.25 2.25 0 0 1-2.25 2.25h-9a2.25 2.25 0 0 1-2.25-2.25v-13A2.25 2.25 0 0 1 8.75 2h9Zm0 1.5h-9a.75.75 0 0 0-.75.75v13c0 .414.336.75.75.75h9a.75.75 0 0 0 .75-.75v-13a.75.75 0 0 0-.75-.75Z" fill="#212121"/></svg> \ No newline at end of file diff --git a/resources/qml/dialogs/ImageOverlay.qml b/resources/qml/dialogs/ImageOverlay.qml index 6f56e79a..fa874529 100644 --- a/resources/qml/dialogs/ImageOverlay.qml +++ b/resources/qml/dialogs/ImageOverlay.qml @@ -29,6 +29,17 @@ Window { onActivated: imageOverlay.close() } + Shortcut { + sequence: StandardKey.Copy + onActivated: { + if (room) { + room.copyMedia(eventId); + } else { + TimelineManager.copyImage(url); + } + } + } + TapHandler { onSingleTapped: imageOverlay.close(); } @@ -111,10 +122,33 @@ Window { height: 48 width: 48 hoverEnabled: true + image: ":/icons/icons/ui/copy.svg" + + //ToolTip.visible: hovered + //ToolTip.delay: Nheko.tooltipDelay + //ToolTip.text: qsTr("Copy to clipboard") + + onClicked: { + imageOverlay.hide(); + if (room) { + room.copyMedia(eventId); + } else { + TimelineManager.copyImage(url); + } + imageOverlay.close(); + } + } + + ImageButton { + height: 48 + width: 48 + hoverEnabled: true image: ":/icons/icons/ui/download.svg" + //ToolTip.visible: hovered //ToolTip.delay: Nheko.tooltipDelay //ToolTip.text: qsTr("Download") + onClicked: { imageOverlay.hide(); if (room) { @@ -130,9 +164,11 @@ Window { width: 48 hoverEnabled: true image: ":/icons/icons/ui/dismiss.svg" + //ToolTip.visible: hovered //ToolTip.delay: Nheko.tooltipDelay //ToolTip.text: qsTr("Close") + onClicked: imageOverlay.close() } } diff --git a/resources/res.qrc b/resources/res.qrc index 3f1b2b65..412cdb83 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -9,6 +9,7 @@ <file>icons/ui/checkmark.svg</file> <file>icons/ui/clock.svg</file> <file>icons/ui/collapsed.svg</file> + <file>icons/ui/copy.svg</file> <file>icons/ui/delete.svg</file> <file>icons/ui/dismiss.svg</file> <file>icons/ui/dismiss_edit.svg</file> diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index 879ec7cc..3a626a3c 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -148,6 +148,7 @@ InputBar::insertMimeData(const QMimeData *md) nhlog::ui()->debug("Got mime formats: {}", md->formats().join(QStringLiteral(", ")).toStdString()); + nhlog::ui()->debug("Has image: {}", md->hasImage()); const auto formats = md->formats().filter(QStringLiteral("/")); const auto image = formats.filter(QStringLiteral("image/"), Qt::CaseInsensitive); const auto audio = formats.filter(QStringLiteral("audio/"), Qt::CaseInsensitive); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index f80f2ee9..5996bea8 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -12,6 +12,7 @@ #include <QDesktopServices> #include <QFileDialog> #include <QGuiApplication> +#include <QMimeData> #include <QMimeDatabase> #include <QRegularExpression> #include <QStandardPaths> @@ -1860,6 +1861,60 @@ TimelineModel::saveMedia(const QString &eventId) const return true; } +bool +TimelineModel::copyMedia(const QString &eventId) const +{ + auto event = events.get(eventId.toStdString(), ""); + if (!event) + return false; + + QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event)); + QString mimeType = QString::fromStdString(mtx::accessors::mimetype(*event)); + qml_mtx_events::EventType eventType = toRoomEventType(*event); + + auto encryptionInfo = mtx::accessors::file(*event); + + const auto url = mxcUrl.toStdString(); + + http::client()->download( + url, + [url, mimeType, eventType, encryptionInfo](const std::string &data, + const std::string &, + const std::string &, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve media {}: {} {}", + url, + err->matrix_error.error, + static_cast<int>(err->status_code)); + return; + } + + try { + auto temp = data; + if (encryptionInfo) + temp = + mtx::crypto::to_string(mtx::crypto::decrypt_file(temp, encryptionInfo.value())); + + auto by = QByteArray(temp.data(), (qsizetype)temp.size()); + QMimeData *clipContents = new QMimeData(); + clipContents->setData(mimeType, by); + + if (eventType == qml_mtx_events::EventType::ImageMessage) { + auto img = utils::readImage(QByteArray(data.data(), (qsizetype)data.size())); + clipContents->setImageData(img); + } + + QGuiApplication::clipboard()->setMimeData(clipContents); + + return; + } catch (const std::exception &e) { + nhlog::ui()->warn("Error while copying file to clipboard: {}", e.what()); + } + }); + return true; +} + void TimelineModel::cacheMedia(const QString &eventId, const std::function<void(const QString)> &callback) diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 0244c1b1..b0d81441 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -320,6 +320,7 @@ public: Q_INVOKABLE void openMedia(const QString &eventId); Q_INVOKABLE void cacheMedia(const QString &eventId); Q_INVOKABLE bool saveMedia(const QString &eventId) const; + Q_INVOKABLE bool copyMedia(const QString &eventId) const; Q_INVOKABLE void showEvent(QString eventId); Q_INVOKABLE void copyLinkToEvent(const QString &eventId) const; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index b949e4c3..44f288c6 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -5,7 +5,9 @@ #include "TimelineViewManager.h" #include <QApplication> +#include <QClipboard> #include <QFileDialog> +#include <QMimeData> #include <QStandardPaths> #include <QString> @@ -29,8 +31,6 @@ #include "voip/CallManager.h" #include "voip/WebRTCSession.h" -namespace msgs = mtx::events::msg; - namespace { template<template<class...> class Op, class... Args> using is_detected = typename nheko::detail::detector<nheko::nonesuch, void, Op, Args...>::value_t; @@ -319,6 +319,37 @@ TimelineViewManager::saveMedia(QString mxcUrl) } void +TimelineViewManager::copyImage(const QString &mxcUrl) const +{ + const auto url = mxcUrl.toStdString(); + QString mimeType; + + http::client()->download( + url, + [url, mimeType](const std::string &data, + const std::string &, + const std::string &, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve media {}: {} {}", + url, + err->matrix_error.error, + static_cast<int>(err->status_code)); + return; + } + + try { + auto img = utils::readImage(QByteArray(data.data(), (qsizetype)data.size())); + QGuiApplication::clipboard()->setImage(img); + + return; + } catch (const std::exception &e) { + nhlog::ui()->warn("Error while copying file to clipboard: {}", e.what()); + } + }); +} + +void TimelineViewManager::updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids) { diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index ee5cf031..e3279e21 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -56,6 +56,7 @@ public: double proportionalHeight); Q_INVOKABLE void openImagePackSettings(QString roomid); Q_INVOKABLE void saveMedia(QString mxcUrl); + Q_INVOKABLE void copyImage(const QString &mxcUrl) const; Q_INVOKABLE QColor userColor(QString id, QColor background); Q_INVOKABLE QString escapeEmoji(QString str) const; Q_INVOKABLE QString htmlEscape(QString str) const { return str.toHtmlEscaped(); } |