diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6a98bc1c..3fbd8069 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -310,7 +310,6 @@ set(SRC_FILES
# Dialogs
src/dialogs/CreateRoom.cpp
src/dialogs/FallbackAuth.cpp
- src/dialogs/ImageOverlay.cpp
src/dialogs/PreviewUploadOverlay.cpp
src/dialogs/ReCaptcha.cpp
@@ -519,7 +518,6 @@ qt5_wrap_cpp(MOC_HEADERS
# Dialogs
src/dialogs/CreateRoom.h
src/dialogs/FallbackAuth.h
- src/dialogs/ImageOverlay.h
src/dialogs/PreviewUploadOverlay.h
src/dialogs/ReCaptcha.h
diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml
index 85ae783d..c85b641a 100644
--- a/resources/qml/Root.qml
+++ b/resources/qml/Root.qml
@@ -136,6 +136,14 @@ Page {
}
+ Component {
+ id: imageOverlay
+
+ ImageOverlay {
+ }
+
+ }
+
Shortcut {
sequence: "Ctrl+K"
onActivated: {
@@ -234,6 +242,15 @@ Page {
dialog.open();
}
+ function onShowImageOverlay(room, eventId, url, proportionalHeight, originalWidth) {
+ var dialog = imageOverlay.createObject(timelineRoot, {
+ "room": room,
+ "eventId": eventId,
+ "url": url
+ });
+ dialog.showFullScreen();
+ }
+
target: TimelineManager
}
diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml
index 5e04b8fd..0bbb43cf 100644
--- a/resources/qml/delegates/ImageMessage.qml
+++ b/resources/qml/delegates/ImageMessage.qml
@@ -63,10 +63,9 @@ Item {
}
TapHandler {
- // TODO(Nico): Replace this with a qml thingy, that also can show animated images
- enabled: type == MtxEvent.ImageMessage && (img.status == Image.Ready || mxcimage.loaded)
+ //enabled: type == MtxEvent.ImageMessage && (img.status == Image.Ready || mxcimage.loaded)
onSingleTapped: {
- TimelineManager.openImageOverlay(url, room.data.eventId);
+ TimelineManager.openImageOverlay(room, url, eventId);
eventPoint.accepted = true;
}
gesturePolicy: TapHandler.ReleaseWithinBounds
diff --git a/resources/qml/dialogs/ImageOverlay.qml b/resources/qml/dialogs/ImageOverlay.qml
new file mode 100644
index 00000000..246a0bab
--- /dev/null
+++ b/resources/qml/dialogs/ImageOverlay.qml
@@ -0,0 +1,110 @@
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtQuick 2.15
+import QtQuick.Window 2.15
+
+import ".."
+
+import im.nheko 1.0
+
+Window {
+ id: imageOverlay
+
+ required property string url
+ required property string eventId
+ required property Room room
+
+ flags: Qt.FramelessWindowHint
+
+ visibility: Window.FullScreen
+ color: Qt.rgba(0.2,0.2,0.2,0.66)
+
+ Shortcut {
+ sequence: StandardKey.Cancel
+ onActivated: imageOverlay.close()
+ }
+
+
+ Item {
+ height: Math.min(parent.height, img.implicitHeight)
+ width: Math.min(parent.width, img.implicitWidth)
+ x: (parent.width - img.width)/2
+ y: (parent.height - img.height)/2
+
+ Image {
+ id: img
+
+ visible: !mxcimage.loaded
+ anchors.fill: parent
+ source: url.replace("mxc://", "image://MxcImage/")
+ asynchronous: true
+ fillMode: Image.PreserveAspectFit
+ smooth: true
+ mipmap: true
+ }
+
+ MxcAnimatedImage {
+ id: mxcimage
+
+ visible: loaded
+ anchors.fill: parent
+ roomm: imageOverlay.room
+ play: !Settings.animateImagesOnHover || mouseArea.hovered
+ eventId: imageOverlay.eventId
+ }
+
+ PinchHandler {
+ }
+
+ WheelHandler {
+ property: "scale"
+ }
+
+ DragHandler {
+ }
+
+ HoverHandler {
+ id: mouseArea
+ }
+ }
+
+
+
+ Row {
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.margins: Nheko.paddingLarge
+ spacing: Nheko.paddingMedium
+
+ 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: {
+ if (room) {
+ room.saveMedia(eventId);
+ } else {
+ TimelineManager.saveMedia(url);
+ }
+ imageOverlay.close();
+ }
+ }
+ ImageButton {
+ height: 48
+ 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/qml/dialogs/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml
index 04f21f55..29ce2c3f 100644
--- a/resources/qml/dialogs/UserProfile.qml
+++ b/resources/qml/dialogs/UserProfile.qml
@@ -70,7 +70,7 @@ ApplicationWindow {
displayName: profile.displayName
userid: profile.userid
Layout.alignment: Qt.AlignHCenter
- onClicked: TimelineManager.openImageOverlay(profile.avatarUrl, "")
+ onClicked: TimelineManager.openImageOverlay(null, profile.avatarUrl, "")
ImageButton {
hoverEnabled: true
diff --git a/resources/qml/voip/ActiveCallBar.qml b/resources/qml/voip/ActiveCallBar.qml
index 677fe40b..a8a65421 100644
--- a/resources/qml/voip/ActiveCallBar.qml
+++ b/resources/qml/voip/ActiveCallBar.qml
@@ -37,7 +37,7 @@ Rectangle {
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
userid: CallManager.callParty
displayName: CallManager.callPartyDisplayName
- onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
+ onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
}
Label {
diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml
index e7247b4f..b2c2dbad 100644
--- a/resources/qml/voip/CallInviteBar.qml
+++ b/resources/qml/voip/CallInviteBar.qml
@@ -44,7 +44,7 @@ Rectangle {
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
userid: CallManager.callParty
displayName: CallManager.callPartyDisplayName
- onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
+ onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
}
Label {
diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml
index 632c0496..1404adf9 100644
--- a/resources/qml/voip/PlaceCall.qml
+++ b/resources/qml/voip/PlaceCall.qml
@@ -81,7 +81,7 @@ Popup {
url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
displayName: room.roomName
roomid: room.roomid
- onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
+ onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
}
Button {
diff --git a/resources/res.qrc b/resources/res.qrc
index bc3a8bd2..d825699a 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -133,6 +133,7 @@
qml/device-verification/NewVerificationRequest.qml
qml/device-verification/Success.qml
qml/device-verification/Waiting.qml
+ qml/dialogs/ImageOverlay.qml
qml/dialogs/ImagePackEditorDialog.qml
qml/dialogs/ImagePackSettingsDialog.qml
qml/dialogs/PhoneNumberInputDialog.qml
diff --git a/src/dialogs/ImageOverlay.cpp b/src/dialogs/ImageOverlay.cpp
deleted file mode 100644
index d9f0a993..00000000
--- a/src/dialogs/ImageOverlay.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-// SPDX-FileCopyrightText: 2017 Konstantinos Sideris
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-// SPDX-FileCopyrightText: 2022 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include
-#include
-#include
-#include
-
-#include "dialogs/ImageOverlay.h"
-
-#include "Utils.h"
-
-using namespace dialogs;
-
-ImageOverlay::ImageOverlay(const QPixmap &image, QWidget *parent)
- : QWidget{parent}
- , originalImage_{image}
-{
- setMouseTracking(true);
- setParent(nullptr);
-
- setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
- setWindowRole(QStringLiteral("imageoverlay"));
-
- setAttribute(Qt::WA_NoSystemBackground, true);
- setAttribute(Qt::WA_TranslucentBackground, true);
- setAttribute(Qt::WA_DeleteOnClose, true);
- setWindowState(Qt::WindowFullScreen);
- close_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this);
-
- connect(close_shortcut_, &QShortcut::activated, this, &ImageOverlay::closing);
- connect(this, &ImageOverlay::closing, this, &ImageOverlay::close);
-
- raise();
-}
-
-void
-ImageOverlay::paintEvent(QPaintEvent *event)
-{
- Q_UNUSED(event);
-
- QPainter painter(this);
- painter.setRenderHint(QPainter::Antialiasing);
-
- // Full screen overlay.
- painter.fillRect(QRect(0, 0, width(), height()), QColor(55, 55, 55, 170));
-
- // Left and Right margins
- int outer_margin = width() * 0.12;
- int buttonSize = 36;
- int margin = outer_margin * 0.1;
-
- int max_width = width() - 2 * outer_margin;
- int max_height = height();
-
- image_ = utils::scaleDown(max_width, max_height, originalImage_);
-
- int diff_x = max_width - image_.width();
- int diff_y = max_height - image_.height();
-
- content_ = QRect(outer_margin + diff_x / 2, diff_y / 2, image_.width(), image_.height());
- close_button_ = QRect(width() - margin - buttonSize, margin, buttonSize, buttonSize);
- save_button_ = QRect(width() - (2 * margin) - (2 * buttonSize), margin, buttonSize, buttonSize);
-
- // Draw main content_.
- painter.drawPixmap(content_, image_);
-
- // Draw top right corner X.
- QPen pen;
- pen.setCapStyle(Qt::RoundCap);
- pen.setWidthF(5);
- pen.setColor("gray");
-
- auto center = close_button_.center();
-
- painter.setPen(pen);
- painter.drawLine(center - QPointF(15, 15), center + QPointF(15, 15));
- painter.drawLine(center + QPointF(15, -15), center - QPointF(15, -15));
-
- // Draw download button
- center = save_button_.center();
- painter.drawLine(center - QPointF(0, 15), center + QPointF(0, 15));
- painter.drawLine(center - QPointF(15, 0), center + QPointF(0, 15));
- painter.drawLine(center + QPointF(0, 15), center + QPointF(15, 0));
-}
-
-void
-ImageOverlay::mousePressEvent(QMouseEvent *event)
-{
- if (event->button() != Qt::LeftButton)
- return;
-
- if (close_button_.contains(event->pos()))
- emit closing();
- else if (save_button_.contains(event->pos()))
- emit saving();
- else if (!content_.contains(event->pos()))
- emit closing();
-}
diff --git a/src/dialogs/ImageOverlay.h b/src/dialogs/ImageOverlay.h
deleted file mode 100644
index 9ce2fb09..00000000
--- a/src/dialogs/ImageOverlay.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// SPDX-FileCopyrightText: 2017 Konstantinos Sideris
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-// SPDX-FileCopyrightText: 2022 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#pragma once
-
-#include
-#include
-#include
-#include
-
-namespace dialogs {
-
-class ImageOverlay : public QWidget
-{
- Q_OBJECT
-public:
- ImageOverlay(const QPixmap &image, QWidget *parent = nullptr);
-
-protected:
- void mousePressEvent(QMouseEvent *event) override;
- void paintEvent(QPaintEvent *event) override;
-
-signals:
- void closing();
- void saving();
-
-private:
- QPixmap originalImage_;
- QPixmap image_;
-
- QRect content_;
- QRect close_button_;
- QRect save_button_;
- QShortcut *close_shortcut_;
-};
-} // dialogs
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 6195c24a..7234caa9 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -6,10 +6,12 @@
#include "TimelineViewManager.h"
#include
+#include
#include
#include
#include
#include
+#include
#include
#include "BlurhashProvider.h"
@@ -32,7 +34,6 @@
#include "SingleImagePackModel.h"
#include "UserSettingsPage.h"
#include "UsersModel.h"
-#include "dialogs/ImageOverlay.h"
#include "emoji/EmojiModel.h"
#include "emoji/Provider.h"
#include "encryption/DeviceVerificationFlow.h"
@@ -331,11 +332,6 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
isInitialSync_ = true;
emit initialSyncChanged(true);
});
-
- connect(this,
- &TimelineViewManager::openImageOverlayInternalCb,
- this,
- &TimelineViewManager::openImageOverlayInternal);
}
void
@@ -416,23 +412,13 @@ TimelineViewManager::escapeEmoji(QString str) const
}
void
-TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId)
+TimelineViewManager::openImageOverlay(TimelineModel *room, QString mxcUrl, QString eventId)
{
if (mxcUrl.isEmpty()) {
return;
}
- MxcImageProvider::download(mxcUrl.remove(QStringLiteral("mxc://")),
- QSize(),
- [this, eventId](QString, QSize, QImage img, QString) {
- if (img.isNull()) {
- nhlog::ui()->error(
- "Error when retrieving image for overlay.");
- return;
- }
-
- emit openImageOverlayInternalCb(eventId, std::move(img));
- });
+ emit showImageOverlay(room, eventId, mxcUrl);
}
void
@@ -443,25 +429,46 @@ TimelineViewManager::openImagePackSettings(QString roomid)
}
void
-TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img)
+TimelineViewManager::saveMedia(QString mxcUrl)
{
- auto pixmap = QPixmap::fromImage(img);
+ const QString downloadsFolder =
+ QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
+ const QString openLocation = downloadsFolder + "/" + mxcUrl.splitRef(u'/').constLast();
- auto imgDialog = new dialogs::ImageOverlay(pixmap);
- imgDialog->showFullScreen();
+ const QString filename = QFileDialog::getSaveFileName(getWidget(), {}, openLocation);
- auto room = rooms_->currentRoom();
- connect(imgDialog, &dialogs::ImageOverlay::saving, room, [eventId, imgDialog, room]() {
- // hide the overlay while presenting the save dialog for better
- // cross platform support.
- imgDialog->hide();
+ if (filename.isEmpty())
+ return;
- if (!room->saveMedia(eventId)) {
- imgDialog->show();
- } else {
- imgDialog->close();
- }
- });
+ 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(err->status_code));
+ return;
+ }
+
+ try {
+ QFile file(filename);
+
+ if (!file.open(QIODevice::WriteOnly))
+ return;
+
+ file.write(QByteArray(data.data(), (int)data.size()));
+ file.close();
+
+ return;
+ } catch (const std::exception &e) {
+ nhlog::ui()->warn("Error while saving file to: {}", e.what());
+ }
+ });
}
void
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 47a72412..455702f4 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -60,8 +60,9 @@ public:
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
bool isWindowFocused() const { return isWindowFocused_; }
- Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId);
+ Q_INVOKABLE void openImageOverlay(TimelineModel *room, QString mxcUrl, QString eventId);
Q_INVOKABLE void openImagePackSettings(QString roomid);
+ Q_INVOKABLE void saveMedia(QString mxcUrl);
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(); }
@@ -85,13 +86,13 @@ signals:
void narrowViewChanged();
void focusChanged();
void focusInput();
- void openImageOverlayInternalCb(QString eventId, QImage img);
void openRoomMembersDialog(MemberList *members, TimelineModel *room);
void openRoomSettingsDialog(RoomSettings *settings);
void openInviteUsersDialog(InviteesModel *invitees);
void openProfile(UserProfile *profile);
void showImagePackSettings(TimelineModel *room, ImagePackListModel *packlist);
void openLeaveRoomDialog(QString roomid);
+ void showImageOverlay(TimelineModel *room, QString eventId, QString url);
public slots:
void updateReadReceipts(const QString &room_id, const std::vector &event_ids);
@@ -120,9 +121,6 @@ public slots:
RoomlistModel *rooms() { return rooms_; }
-private slots:
- void openImageOverlayInternal(QString eventId, QImage img);
-
private:
#ifdef USE_QUICK_VIEW
QQuickView *view;
diff --git a/src/ui/MxcAnimatedImage.cpp b/src/ui/MxcAnimatedImage.cpp
index 36e028e3..83787fff 100644
--- a/src/ui/MxcAnimatedImage.cpp
+++ b/src/ui/MxcAnimatedImage.cpp
@@ -20,10 +20,12 @@
void
MxcAnimatedImage::startDownload()
{
+ nhlog::ui()->debug("START DOWNLOAD!!!");
if (!room_)
return;
if (eventId_.isEmpty())
return;
+ nhlog::ui()->debug("START DOWNLOAD2!!!");
auto event = room_->eventById(eventId_);
if (!event) {
@@ -92,7 +94,9 @@ MxcAnimatedImage::startDownload()
"Playing movie with size: {}, {}", buffer.bytesAvailable(), buffer.isOpen());
movie.setFormat(mimeType);
movie.setDevice(&buffer);
- movie.setScaledSize(this->size().toSize());
+
+ if (height() != 0 && width() != 0)
+ movie.setScaledSize(this->size().toSize());
if (buffer.bytesAvailable() <
4LL * 1024 * 1024 * 1024) // cache images smaller than 4MB in RAM
movie.setCacheMode(QMovie::CacheAll);
@@ -145,6 +149,17 @@ MxcAnimatedImage::startDownload()
});
}
+void
+MxcAnimatedImage::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
+{
+ QQuickItem::geometryChanged(newGeometry, oldGeometry);
+
+ if (newGeometry.size() != oldGeometry.size()) {
+ if (height() != 0 && width() != 0)
+ movie.setScaledSize(newGeometry.size().toSize());
+ }
+}
+
QSGNode *
MxcAnimatedImage::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
{
@@ -171,8 +186,8 @@ MxcAnimatedImage::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeD
return nullptr;
}
- n->setRect(QRect(0, 0, width(), height()));
- n->setFiltering(QSGTexture::Nearest);
+ n->setRect(0, 0, width(), height());
+ n->setFiltering(QSGTexture::Linear);
n->setMipmapFiltering(QSGTexture::None);
return n;
diff --git a/src/ui/MxcAnimatedImage.h b/src/ui/MxcAnimatedImage.h
index 1afe6a19..8891e57e 100644
--- a/src/ui/MxcAnimatedImage.h
+++ b/src/ui/MxcAnimatedImage.h
@@ -60,6 +60,7 @@ public:
}
}
+ void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
QSGNode *updatePaintNode(QSGNode *oldNode,
QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override;