// SPDX-FileCopyrightText: 2019 Black Hat // SPDX-License-Identifier: GPL-3.0-only import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import Qt.labs.platform 1.1 import org.kde.kirigami 2.15 as Kirigami Popup { id: root property alias source: image.source property string filename property string blurhash: "" property int imageWidth: -1 property int imageHeight: -1 property var modelData parent: Overlay.overlay closePolicy: Popup.CloseOnEscape width: parent.width height: parent.height modal: true padding: 0 background: null ColumnLayout { anchors.fill: parent spacing: Kirigami.Units.largeSpacing Control { Layout.fillWidth: true contentItem: RowLayout { spacing: Kirigami.Units.largeSpacing Kirigami.Avatar { id: avatar Layout.preferredWidth: Kirigami.Units.iconSizes.medium Layout.preferredHeight: Kirigami.Units.iconSizes.medium name: modelData.author.name ?? modelData.author.displayName source: modelData.author.avatarMediaId ? ("image://mxc/" + modelData.author.avatarMediaId) : "" color: modelData.author.color } ColumnLayout { Layout.fillWidth: true spacing: 0 Label { id: nameLabel text: modelData.author.displayName textFormat: Text.PlainText font.weight: Font.Bold color: author.color } Label { id: timeLabel text: time.toLocaleString(Qt.locale(), Locale.ShortFormat) } } Label { id: imageLabel Layout.fillWidth: true Layout.leftMargin: Kirigami.Units.largeSpacing text: modelData.display font.weight: Font.Bold elide: Text.ElideRight } ToolButton { Layout.preferredWidth: Kirigami.Units.gridUnit * 2 Layout.preferredHeight: Kirigami.Units.gridUnit * 2 text: i18n("Zoom in") Accessible.name: text icon.name: "zoom-in" display: AbstractButton.IconOnly onClicked: { image.scaleFactor = image.scaleFactor + 0.25 if (image.scaleFactor > 3) { image.scaleFactor = 3 } } ToolTip.text: text ToolTip.delay: Kirigami.Units.toolTipDelay ToolTip.visible: hovered } ToolButton { Layout.preferredWidth: Kirigami.Units.gridUnit * 2 Layout.preferredHeight: Kirigami.Units.gridUnit * 2 text: i18n("Zoom out") Accessible.name: text icon.name: "zoom-out" display: AbstractButton.IconOnly onClicked: { image.scaleFactor = image.scaleFactor - 0.25 if (image.scaleFactor < 0.25) { image.scaleFactor = 0.25 } } ToolTip.text: text ToolTip.delay: Kirigami.Units.toolTipDelay ToolTip.visible: hovered } ToolButton { Layout.preferredWidth: Kirigami.Units.gridUnit * 2 Layout.preferredHeight: Kirigami.Units.gridUnit * 2 text: i18n("Rotate left") Accessible.name: text icon.name: "image-rotate-left-symbolic" display: AbstractButton.IconOnly onClicked: image.rotationAngle = image.rotationAngle - 90 ToolTip.text: text ToolTip.delay: Kirigami.Units.toolTipDelay ToolTip.visible: hovered } ToolButton { Layout.preferredWidth: Kirigami.Units.gridUnit * 2 Layout.preferredHeight: Kirigami.Units.gridUnit * 2 text: i18n("Rotate right") Accessible.name: text icon.name: "image-rotate-right-symbolic" display: AbstractButton.IconOnly onClicked: image.rotationAngle = image.rotationAngle + 90 ToolTip.text: text ToolTip.delay: Kirigami.Units.toolTipDelay ToolTip.visible: hovered } ToolButton { Layout.preferredWidth: Kirigami.Units.gridUnit * 2 Layout.preferredHeight: Kirigami.Units.gridUnit * 2 text: i18n("Save as") Accessible.name: text icon.name: "document-save" display: AbstractButton.IconOnly onClicked: { var dialog = saveAsDialog.createObject(ApplicationWindow.overlay) dialog.open() dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId) } ToolTip.text: text ToolTip.delay: Kirigami.Units.toolTipDelay ToolTip.visible: hovered } ToolButton { Layout.preferredWidth: Kirigami.Units.gridUnit * 2 Layout.preferredHeight: Kirigami.Units.gridUnit * 2 text: i18n("Close") Accessible.name: text icon.name: "dialog-close" display: AbstractButton.IconOnly onClicked: { root.close() } ToolTip.text: text ToolTip.delay: Kirigami.Units.toolTipDelay ToolTip.visible: hovered } } background: Rectangle { color: Kirigami.Theme.alternateBackgroundColor } Kirigami.Separator { anchors { left: parent.left right: parent.right bottom: parent.bottom } height: 1 } } BusyIndicator { Layout.fillWidth: true visible: image.status !== Image.Ready && root.blurhash === "" running: visible } // Provides container to fill the space that isn't taken up by the top controls and clips the image when zooming makes it larger than the available area. Item { id: imageContainer Layout.fillWidth: true Layout.fillHeight: true Layout.leftMargin: Kirigami.Units.largeSpacing Layout.rightMargin: Kirigami.Units.largeSpacing Layout.bottomMargin: Kirigami.Units.largeSpacing clip: true Image { id: image property var scaleFactor: 1 property int rotationAngle: 0 property var rotationInsensitiveWidth: Math.min(root.imageWidth !== -1 ? root.imageWidth : sourceSize.width, imageContainer.width - Kirigami.Units.largeSpacing * 2) property var rotationInsensitiveHeight: Math.min(root.imageHeight !== -1 ? root.imageHeight : sourceSize.height, imageContainer.height - Kirigami.Units.largeSpacing * 2) anchors.centerIn: parent width: rotationAngle % 180 === 0 ? rotationInsensitiveWidth : rotationInsensitiveHeight height: rotationAngle % 180 === 0 ? rotationInsensitiveHeight : rotationInsensitiveWidth fillMode: Image.PreserveAspectFit clip: true Behavior on width { NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic} } Behavior on height { NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic} } Image { anchors.centerIn: parent width: image.width height: image.height source: root.blurhash !== "" ? ("image://blurhash/" + root.blurhash) : "" visible: root.blurhash !== "" && parent.status !== Image.Ready } transform: [ Rotation { origin.x: image.width / 2 origin.y: image.height / 2 angle: image.rotationAngle Behavior on angle { RotationAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic} } }, Scale { origin.x: image.width / 2 origin.y: image.height / 2 xScale: image.scaleFactor yScale: image.scaleFactor Behavior on xScale { NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic} } Behavior on yScale { NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic} } } ] MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: { const contextMenu = fileDelegateContextMenu.createObject(parent, { author: modelData.author, message: modelData.message, eventId: modelData.eventId, source: modelData.source, file: root.parent, mimeType: modelData.mimeType, progressInfo: modelData.progressInfo, plainMessage: modelData.message, }); contextMenu.closeFullscreen.connect(root.close) contextMenu.open(); } } } MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton onClicked: { root.close() } } } } Component { id: saveAsDialog FileDialog { fileMode: FileDialog.SaveFile folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation) onAccepted: { if (!currentFile) { return; } currentRoom.downloadFile(eventId, currentFile) } } } onClosed: { image.scaleFactor = 1 image.rotationAngle = 0 } }