Warning, /graphics/koko/src/qml/EditorView.qml is written in an unsupported language. File is not indexed.

0001 /*
0002  * SPDX-FileCopyrightText: 2017 Atul Sharma <atulsharma406@gmail.com>
0003  * SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006  */
0007 
0008 import QtQuick 2.15
0009 import QtQml 2.15
0010 import QtQuick.Templates 2.15 as T
0011 import QtQuick.Controls 2.15 as QQC2
0012 import QtQuick.Layouts 1.15
0013 import QtQuick.Dialogs 1.3
0014 import org.kde.kirigami 2.15 as Kirigami
0015 import org.kde.kquickimageeditor 1.0 as KQuickImageEditor
0016 import "./Dialog"
0017 
0018 Kirigami.Page {
0019     id: root
0020 
0021     property bool cropping: false
0022     property bool resizing: false
0023     property string imagePath
0024     property bool forceDiscard: false
0025 
0026     signal imageEdited()
0027 
0028     title: i18n("Edit")
0029 
0030     leftPadding: 0
0031     rightPadding: 0
0032     topPadding: 0
0033     bottomPadding: 0
0034 
0035     onBackRequested: (event) => {
0036         if (imageDoc.edited && !root.forceDiscard) {
0037             confirmDiscardingChangeDialog.visible = true;
0038             event.accepted = true;
0039         }
0040     }
0041 
0042     function crop() {
0043         root.cropping = false
0044         imageDoc.crop(selectionTool.selectionX / editImage.ratioX,
0045                       selectionTool.selectionY / editImage.ratioY,
0046                       selectionTool.selectionWidth / editImage.ratioX,
0047                       selectionTool.selectionHeight / editImage.ratioY);
0048     }
0049 
0050     function resize() {
0051         const ratioX = editImage.paintedWidth / editImage.nativeWidth;
0052         const ratioY = editImage.paintedHeight / editImage.nativeHeight;
0053         root.resizing = false
0054         imageDoc.resize(selectionTool.selectionWidth / ratioX, selectionTool.selectionHeight / ratioY);
0055     }
0056 
0057     actions {
0058         main: Kirigami.Action {
0059             id: saveAction
0060             visible: imageDoc.edited
0061             text: i18nc("@action:button Save image modification", "Save")
0062             icon.name: "document-save"
0063             onTriggered: {
0064                 if (!imageDoc.save()) {
0065                     msg.type = Kirigami.MessageType.Error
0066                     msg.text = i18n("Unable to save file. Check if you have the correct permission to edit this file.")
0067                     msg.visible = true;
0068                 }
0069                 root.imageEdited();
0070                 applicationWindow().pageStack.layers.pop();
0071             }
0072         }
0073         left: Kirigami.Action {
0074             id: undoAction
0075             text: i18nc("@action:button Undo modification", "Undo")
0076             icon.name: "edit-undo"
0077             onTriggered: {
0078                 if (imageDoc.edited) {
0079                     imageDoc.undo();
0080                 }
0081             }
0082             visible: imageDoc.edited
0083         }
0084         contextualActions: [
0085             Kirigami.Action {
0086                 icon.name: root.cropping ? "dialog-cancel" : "transform-crop"
0087                 text: root.cropping ? i18nc("@action:button", "Cancel") : i18nc("@action:button Crop an image", "Crop");
0088                 onTriggered: root.cropping = !root.cropping;
0089                 visible: !root.resizing
0090             },
0091             Kirigami.Action {
0092                 icon.name: "dialog-ok"
0093                 text: i18nc("@action:button Crop an image", "Crop");
0094                 onTriggered: root.crop();
0095                 visible: root.cropping
0096             },
0097             Kirigami.Action {
0098                 icon.name: root.resizing ? "dialog-cancel" : "transform-scale"
0099                 text: root.resizing ? i18nc("@action:button", "Cancel") : i18nc("@action:button Resize an image", "Resize");
0100                 onTriggered: root.resizing = !root.resizing;
0101                 visible: !root.cropping
0102             },
0103             Kirigami.Action {
0104                 icon.name: "dialog-ok"
0105                 text: i18nc("@action:button Resize an image", "Resize");
0106                 onTriggered: root.resize();
0107                 visible: root.resizing
0108             },
0109             Kirigami.Action {
0110                 icon.name: "object-rotate-left"
0111                 text: i18nc("@action:button Rotate an image to the left", "Rotate left");
0112                 onTriggered: imageDoc.rotate(-90);
0113                 visible: !root.cropping && !root.resizing
0114             },
0115             Kirigami.Action {
0116                 icon.name: "object-rotate-right"
0117                 text: i18nc("@action:button Rotate an image to the right", "Rotate right");
0118                 onTriggered: imageDoc.rotate(90);
0119                 visible: !root.cropping && !root.resizing
0120             },
0121             Kirigami.Action {
0122                 icon.name: "object-flip-vertical"
0123                 text: i18nc("@action:button Mirror an image vertically", "Flip");
0124                 onTriggered: imageDoc.mirror(false, true);
0125                 visible: !root.cropping && !root.resizing
0126             },
0127             Kirigami.Action {
0128                 icon.name: "object-flip-horizontal"
0129                 text: i18nc("@action:button Mirror an image horizontally", "Mirror");
0130                 onTriggered: imageDoc.mirror(true, false);
0131                 visible: !root.cropping && !root.resizing
0132             },
0133             Kirigami.Action {
0134                 visible: root.resizing
0135                 displayComponent: QQC2.ToolSeparator {
0136                     leftPadding: Kirigami.Units.largeSpacing
0137                     rightPadding: leftPadding
0138                 }
0139             },
0140             Kirigami.Action {
0141                 visible: root.resizing
0142                 displayComponent: QQC2.Label {
0143                     text: i18nc("@title:group for crop area size spinboxes", "Size:")
0144                 }
0145             },
0146             Kirigami.Action {
0147                 visible: root.resizing
0148                 displayComponent: EditorSpinBox {
0149                     minimumContentWidth: widthTextMetrics.width
0150                     from: 1
0151                     to: editImage.nativeWidth
0152                     value: selectionTool.selectionWidth / editImage.ratioX
0153                     onValueModified: selectionTool.selectionWidth = value * editImage.ratioX
0154                 }
0155             },
0156             Kirigami.Action {
0157                 visible: root.resizing
0158                 displayComponent: EditorSpinBox {
0159                     minimumContentWidth: heightTextMetrics.width
0160                     from: 1
0161                     to: editImage.nativeHeight
0162                     value: selectionTool.selectionHeight / editImage.ratioY
0163                     onValueModified: selectionTool.selectionHeight = value * editImage.ratioY
0164                 }
0165             },
0166             Kirigami.Action {
0167                 visible: root.resizing
0168                 displayComponent: Item {
0169                     implicitWidth: Kirigami.Units.largeSpacing
0170                 }
0171             },
0172             Kirigami.Action {
0173                 visible: root.resizing
0174                 displayComponent: QQC2.Label {
0175                     text: i18nc("@title:group for crop area position spinboxes", "Position:")
0176                 }
0177             },
0178             Kirigami.Action {
0179                 visible: root.resizing
0180                 displayComponent: EditorSpinBox {
0181                     minimumContentWidth: widthTextMetrics.width
0182                     from: 0
0183                     to: editImage.nativeWidth - (selectionTool.selectionWidth / editImage.ratioX)
0184                     value: selectionTool.selectionX / editImage.ratioX
0185                     onValueModified: selectionTool.selectionX = value * editImage.ratioX
0186                 }
0187             },
0188             Kirigami.Action {
0189                 visible: root.resizing
0190                 displayComponent: EditorSpinBox {
0191                     minimumContentWidth: heightTextMetrics.width
0192                     from: 0
0193                     to: editImage.nativeHeight - (selectionTool.selectionHeight / editImage.ratioY)
0194                     value: selectionTool.selectionY / editImage.ratioY
0195                     onValueModified: selectionTool.selectionY = value * editImage.ratioY
0196                 }
0197             }
0198         ]
0199     }
0200 
0201     ConfirmDiscardingChange {
0202         id: confirmDiscardingChangeDialog
0203         onDiscardChanges: {
0204             root.forceDiscard = true;
0205             applicationWindow().pageStack.layers.pop();
0206         }
0207     }
0208 
0209     TextMetrics {
0210         id: widthTextMetrics
0211         text: editImage.nativeWidth.toLocaleString(root.locale, 'f', 0)
0212     }
0213 
0214     TextMetrics {
0215         id: heightTextMetrics
0216         text: editImage.nativeHeight.toLocaleString(root.locale, 'f', 0)
0217     }
0218 
0219     component EditorSpinBox : QQC2.SpinBox {
0220         id: control
0221         property real minimumContentWidth: 0
0222         contentItem: QQC2.TextField {
0223             id: textField
0224             implicitWidth: control.minimumContentWidth + leftPadding + rightPadding
0225             implicitHeight: Math.ceil(contentHeight) + topPadding + bottomPadding
0226             palette: control.palette
0227             leftPadding: control.spacing
0228             rightPadding: control.spacing
0229             topPadding: 0
0230             bottomPadding: 0
0231             text: control.displayText
0232             font: control.font
0233             color: Kirigami.Theme.textColor
0234             selectionColor: Kirigami.Theme.highlightColor
0235             selectedTextColor: Kirigami.Theme.highlightedTextColor
0236             horizontalAlignment: Qt.AlignHCenter
0237             verticalAlignment: Qt.AlignVCenter
0238             readOnly: !control.editable
0239             validator: control.validator
0240             inputMethodHints: control.inputMethodHints
0241             selectByMouse: true
0242             background: null
0243         }
0244     }
0245 
0246     FileDialog {
0247         id: fileDialog
0248         title: i18n("Save As")
0249         folder: shortcuts.home
0250         selectMultiple: false
0251         selectExisting: false
0252         onAccepted: {
0253             if (imageDoc.saveAs(fileDialog.fileUrl)) {;
0254                 imagePath = fileDialog.fileUrl;
0255                 msg.type = Kirigami.MessageType.Information
0256                 msg.text = i18n("You are now editing a new file.")
0257                 msg.visible = true;
0258             } else {
0259                 msg.type = Kirigami.MessageType.Error
0260                 msg.text = i18n("Unable to save file. Check if you have the correct permission to edit this file.")
0261                 msg.visible = true;
0262             }
0263             fileDialog.close()
0264         }
0265         onRejected: {
0266             fileDialog.close()
0267         }
0268         Component.onCompleted: visible = false
0269     }
0270 
0271     KQuickImageEditor.ImageItem {
0272         id: editImage
0273         readonly property real ratioX: editImage.paintedWidth / editImage.nativeWidth;
0274         readonly property real ratioY: editImage.paintedHeight / editImage.nativeHeight;
0275 
0276         // Assigning this to the contentItem and setting the padding causes weird positioning issues
0277         anchors.fill: parent
0278         anchors.margins: Kirigami.Units.gridUnit
0279         fillMode: KQuickImageEditor.ImageItem.PreserveAspectFit
0280         image: imageDoc.image
0281 
0282         Shortcut {
0283             sequence: StandardKey.Undo
0284             onActivated: undoAction.trigger();
0285         }
0286 
0287         Shortcut {
0288             sequences: [StandardKey.Save, "Enter"]
0289             onActivated: saveAction.trigger();
0290         }
0291 
0292         Shortcut {
0293             sequence: StandardKey.SaveAs
0294             onActivated: saveAsAction.trigger();
0295         }
0296 
0297         KQuickImageEditor.ImageDocument {
0298             id: imageDoc
0299             path: root.imagePath
0300         }
0301 
0302         KQuickImageEditor.SelectionTool {
0303             id: selectionTool
0304             visible: root.cropping || root.resizing
0305             width: editImage.paintedWidth
0306             height: editImage.paintedHeight
0307             x: editImage.horizontalPadding
0308             y: editImage.verticalPadding
0309             KQuickImageEditor.CropBackground {
0310                 anchors.fill: parent
0311                 z: -1
0312                 insideX: selectionTool.selectionX
0313                 insideY: selectionTool.selectionY
0314                 insideWidth: selectionTool.selectionWidth
0315                 insideHeight: selectionTool.selectionHeight
0316             }
0317             Loader {
0318                 active: root.resizing
0319                 visible: root.resizing
0320                 x: selectionTool.selectionX
0321                 y: selectionTool.selectionY
0322                 width: selectionTool.selectionWidth
0323                 height: selectionTool.selectionHeight
0324                 sourceComponent: KQuickImageEditor.ImageItem {
0325                     anchors.fill: parent
0326                     fillMode: KQuickImageEditor.ImageItem.Stretch
0327                     image: imageDoc.image
0328                 }
0329             }
0330             Connections {
0331                 target: selectionTool.selectionArea
0332                 function onDoubleClicked() {
0333                     if (root.cropping) {
0334                         root.crop()
0335                     } else if (root.resizing) {
0336                         root.resize()
0337                     }
0338                 }
0339             }
0340         }
0341         onImageChanged: {
0342             selectionTool.selectionX = 0
0343             selectionTool.selectionY = 0
0344             selectionTool.selectionWidth = Qt.binding(() => selectionTool.width)
0345             selectionTool.selectionHeight = Qt.binding(() => selectionTool.height)
0346         }
0347     }
0348 
0349     footer: Kirigami.InlineMessage {
0350         id: msg
0351         type: Kirigami.MessageType.Error
0352         showCloseButton: true
0353         visible: false
0354     }
0355 }