mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 03:00:46 +03:00
parent
0ded15315e
commit
6a03615413
8 changed files with 129 additions and 2 deletions
1
resources/icons/ui/copy.svg
Normal file
1
resources/icons/ui/copy.svg
Normal file
|
@ -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>
|
After Width: | Height: | Size: 501 B |
|
@ -29,6 +29,17 @@ Window {
|
||||||
onActivated: imageOverlay.close()
|
onActivated: imageOverlay.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: StandardKey.Copy
|
||||||
|
onActivated: {
|
||||||
|
if (room) {
|
||||||
|
room.copyMedia(eventId);
|
||||||
|
} else {
|
||||||
|
TimelineManager.copyImage(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
onSingleTapped: imageOverlay.close();
|
onSingleTapped: imageOverlay.close();
|
||||||
}
|
}
|
||||||
|
@ -107,14 +118,37 @@ Window {
|
||||||
anchors.margins: Nheko.paddingLarge
|
anchors.margins: Nheko.paddingLarge
|
||||||
spacing: Nheko.paddingMedium
|
spacing: Nheko.paddingMedium
|
||||||
|
|
||||||
|
ImageButton {
|
||||||
|
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 {
|
ImageButton {
|
||||||
height: 48
|
height: 48
|
||||||
width: 48
|
width: 48
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
image: ":/icons/icons/ui/download.svg"
|
image: ":/icons/icons/ui/download.svg"
|
||||||
|
|
||||||
//ToolTip.visible: hovered
|
//ToolTip.visible: hovered
|
||||||
//ToolTip.delay: Nheko.tooltipDelay
|
//ToolTip.delay: Nheko.tooltipDelay
|
||||||
//ToolTip.text: qsTr("Download")
|
//ToolTip.text: qsTr("Download")
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
imageOverlay.hide();
|
imageOverlay.hide();
|
||||||
if (room) {
|
if (room) {
|
||||||
|
@ -130,9 +164,11 @@ Window {
|
||||||
width: 48
|
width: 48
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
image: ":/icons/icons/ui/dismiss.svg"
|
image: ":/icons/icons/ui/dismiss.svg"
|
||||||
|
|
||||||
//ToolTip.visible: hovered
|
//ToolTip.visible: hovered
|
||||||
//ToolTip.delay: Nheko.tooltipDelay
|
//ToolTip.delay: Nheko.tooltipDelay
|
||||||
//ToolTip.text: qsTr("Close")
|
//ToolTip.text: qsTr("Close")
|
||||||
|
|
||||||
onClicked: imageOverlay.close()
|
onClicked: imageOverlay.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
<file>icons/ui/checkmark.svg</file>
|
<file>icons/ui/checkmark.svg</file>
|
||||||
<file>icons/ui/clock.svg</file>
|
<file>icons/ui/clock.svg</file>
|
||||||
<file>icons/ui/collapsed.svg</file>
|
<file>icons/ui/collapsed.svg</file>
|
||||||
|
<file>icons/ui/copy.svg</file>
|
||||||
<file>icons/ui/delete.svg</file>
|
<file>icons/ui/delete.svg</file>
|
||||||
<file>icons/ui/dismiss.svg</file>
|
<file>icons/ui/dismiss.svg</file>
|
||||||
<file>icons/ui/dismiss_edit.svg</file>
|
<file>icons/ui/dismiss_edit.svg</file>
|
||||||
|
|
|
@ -148,6 +148,7 @@ InputBar::insertMimeData(const QMimeData *md)
|
||||||
|
|
||||||
nhlog::ui()->debug("Got mime formats: {}",
|
nhlog::ui()->debug("Got mime formats: {}",
|
||||||
md->formats().join(QStringLiteral(", ")).toStdString());
|
md->formats().join(QStringLiteral(", ")).toStdString());
|
||||||
|
nhlog::ui()->debug("Has image: {}", md->hasImage());
|
||||||
const auto formats = md->formats().filter(QStringLiteral("/"));
|
const auto formats = md->formats().filter(QStringLiteral("/"));
|
||||||
const auto image = formats.filter(QStringLiteral("image/"), Qt::CaseInsensitive);
|
const auto image = formats.filter(QStringLiteral("image/"), Qt::CaseInsensitive);
|
||||||
const auto audio = formats.filter(QStringLiteral("audio/"), Qt::CaseInsensitive);
|
const auto audio = formats.filter(QStringLiteral("audio/"), Qt::CaseInsensitive);
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
|
#include <QMimeData>
|
||||||
#include <QMimeDatabase>
|
#include <QMimeDatabase>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
@ -1860,6 +1861,60 @@ TimelineModel::saveMedia(const QString &eventId) const
|
||||||
return true;
|
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
|
void
|
||||||
TimelineModel::cacheMedia(const QString &eventId,
|
TimelineModel::cacheMedia(const QString &eventId,
|
||||||
const std::function<void(const QString)> &callback)
|
const std::function<void(const QString)> &callback)
|
||||||
|
|
|
@ -320,6 +320,7 @@ public:
|
||||||
Q_INVOKABLE void openMedia(const QString &eventId);
|
Q_INVOKABLE void openMedia(const QString &eventId);
|
||||||
Q_INVOKABLE void cacheMedia(const QString &eventId);
|
Q_INVOKABLE void cacheMedia(const QString &eventId);
|
||||||
Q_INVOKABLE bool saveMedia(const QString &eventId) const;
|
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 showEvent(QString eventId);
|
||||||
Q_INVOKABLE void copyLinkToEvent(const QString &eventId) const;
|
Q_INVOKABLE void copyLinkToEvent(const QString &eventId) const;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
#include "TimelineViewManager.h"
|
#include "TimelineViewManager.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QClipboard>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
|
#include <QMimeData>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
@ -29,8 +31,6 @@
|
||||||
#include "voip/CallManager.h"
|
#include "voip/CallManager.h"
|
||||||
#include "voip/WebRTCSession.h"
|
#include "voip/WebRTCSession.h"
|
||||||
|
|
||||||
namespace msgs = mtx::events::msg;
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
template<template<class...> class Op, class... Args>
|
template<template<class...> class Op, class... Args>
|
||||||
using is_detected = typename nheko::detail::detector<nheko::nonesuch, void, Op, Args...>::value_t;
|
using is_detected = typename nheko::detail::detector<nheko::nonesuch, void, Op, Args...>::value_t;
|
||||||
|
@ -318,6 +318,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
|
void
|
||||||
TimelineViewManager::updateReadReceipts(const QString &room_id,
|
TimelineViewManager::updateReadReceipts(const QString &room_id,
|
||||||
const std::vector<QString> &event_ids)
|
const std::vector<QString> &event_ids)
|
||||||
|
|
|
@ -56,6 +56,7 @@ public:
|
||||||
double proportionalHeight);
|
double proportionalHeight);
|
||||||
Q_INVOKABLE void openImagePackSettings(QString roomid);
|
Q_INVOKABLE void openImagePackSettings(QString roomid);
|
||||||
Q_INVOKABLE void saveMedia(QString mxcUrl);
|
Q_INVOKABLE void saveMedia(QString mxcUrl);
|
||||||
|
Q_INVOKABLE void copyImage(const QString &mxcUrl) const;
|
||||||
Q_INVOKABLE QColor userColor(QString id, QColor background);
|
Q_INVOKABLE QColor userColor(QString id, QColor background);
|
||||||
Q_INVOKABLE QString escapeEmoji(QString str) const;
|
Q_INVOKABLE QString escapeEmoji(QString str) const;
|
||||||
Q_INVOKABLE QString htmlEscape(QString str) const { return str.toHtmlEscaped(); }
|
Q_INVOKABLE QString htmlEscape(QString str) const { return str.toHtmlEscaped(); }
|
||||||
|
|
Loading…
Reference in a new issue