Warning, /utilities/skanpage/src/qml/DocumentPage.qml is written in an unsupported language. File is not indexed.

0001 /**
0002  * SPDX-FileCopyrightText: 2015 by Kåre Särs <kare.sars@iki .fi>
0003  * SPDX-FileCopyrightText: 2021 by Alexander Stippich <a.stippich@gmx.net>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006  */
0007 
0008 import QtQuick 2.15
0009 import QtQuick.Controls 2.14
0010 import QtQuick.Layouts 1.1
0011 import QtQml 2.15
0012 
0013 import org.kde.kirigami 2.12 as Kirigami
0014 import org.kde.kquickimageeditor 1.0 as KQuickImageEditor
0015 import org.kde.skanpage 1.0
0016 
0017 Item {
0018     id: documentPage
0019 
0020     signal saveSinglePage(int pageNumber)
0021 
0022     Connections {
0023         target: skanpage.documentModel
0024 
0025         function onActivePageSourceChanged() {
0026             zoomFitAction.trigger()
0027         }
0028     }
0029 
0030     property bool showPreview: false
0031 
0032     function getScanArea() {
0033         return Qt.rect(selection.selectionX / selection.width, selection.selectionY / selection.height, selection.selectionWidth / selection.width, selection.selectionHeight / selection.height)
0034     }
0035 
0036     component TransparentButton: RoundButton {
0037         visible: action ? action.enabled : true
0038         anchors.margins: Kirigami.Units.smallSpacing
0039         display: AbstractButton.IconOnly
0040         Kirigami.Theme.colorSet: Kirigami.Theme.Button
0041         hoverEnabled: true
0042         opacity: {
0043             if (hovered) return 1.0 // When using a mouse, visible on hover
0044             if (Kirigami.Settings.tabletMode || Kirigami.Settings.hasTransientTouchInput) return 0.75
0045             return 0.5 // When using a mouse, barely visible otherwise, to not get in the way
0046         }
0047         Behavior on opacity { NumberAnimation { duration: Kirigami.Units.smallDuration} }
0048         ToolTip.visible: hovered
0049         ToolTip.delay: Kirigami.Units.toolTipDelay
0050         ToolTip.text: action ? action.text : ""
0051     }
0052 
0053     Action {
0054         id: showPreviewAction
0055         icon.name: "dialog-close"
0056         text: i18n("Show Preview")
0057         shortcut: "Esc"
0058         enabled: !previewImage.null
0059         onTriggered: showPreview = true
0060     }
0061 
0062     Kirigami.PlaceholderMessage {
0063         id: emptyDocumentMessage
0064 
0065         anchors.centerIn: parent
0066         width: parent.width - (Kirigami.Units.largeSpacing * 4)
0067 
0068         visible: skanpage.documentModel.count === 0 && !showPreview && previewImage.null
0069 
0070         icon.name: "document"
0071 
0072         text: xi18nc("@info", "You do not have any images in this document.<nl/><nl/>Start scanning!")
0073     }
0074 
0075     KQuickImageEditor.ImageItem {
0076         id: previewImage
0077         visible: (skanpage.documentModel.count === 0 || showPreview) && !previewImage.null
0078         enabled: visible
0079 
0080         anchors { left: parent.left; right: parent.right; top: parent.top; bottom: selectionToolbar.top }
0081         anchors.margins: Kirigami.Units.largeSpacing
0082         fillMode: KQuickImageEditor.ImageItem.PreserveAspectFit
0083         smooth: true // The classic fuzzyness of low res
0084         image: skanpage.previewImage
0085 
0086         KQuickImageEditor.SelectionTool {
0087             id: selection
0088             visible: skanpage.scanArea.width > 0
0089             enabled: visible
0090             x: previewImage.horizontalPadding + 1
0091             y: previewImage.verticalPadding + 1
0092             width: previewImage.paintedWidth
0093             height: previewImage.paintedHeight
0094 
0095             property bool pressed: pressedHandle || selectionArea.pressed
0096             onPressedChanged: if (!pressed) skanpage.scanArea = getScanArea()
0097 
0098             // A shortcut for scanning the full area seems useful
0099             selectionArea.onDoubleClicked: { // It is updated then releasing the mouse
0100                 skanpage.clearSubAreas()
0101                 selectionX = 0; selectionY = 0; selectionWidth = width; selectionHeight = height
0102             }
0103 
0104             MouseArea {
0105                 id: newAreaSelector
0106                 anchors.fill: parent
0107                 anchors.rightMargin: selection.state === "queueNewArea"
0108                                    ? Kirigami.Units.gridUnit : selection.selectionWidth
0109                 anchors.bottomMargin: selection.state === "queueNewArea"
0110                                     ? Kirigami.Units.gridUnit : selection.selectionHeight
0111                 property bool active: selection.state === "queueNewArea" ||
0112                                       selection.state === "addingNewArea" || selection.state === "sizingNewArea"
0113                 z: active ? 1 : -1
0114 
0115                 onPressed: if (active) {
0116                     selection.state = "sizingNewArea"
0117                 }
0118                 onReleased: if (active) {
0119                     skanpage.scanArea = getScanArea()
0120                     // Add the area as a sub-area to remove nested sub-areas and too overlaping ones
0121                     if (skanpage.appendSubArea(skanpage.scanArea)) // If the area was added
0122                         if (skanpage.scanSubAreas.length >= 1)
0123                             skanpage.eraseSubArea(skanpage.scanSubAreas.length - 1) // Remove last
0124                     selection.state = "idle"
0125                 }
0126                 HoverHandler {
0127                     // This seems to be more reliable than the MouseArea containsMouse, apparently
0128                     // that doesn't update immediately after changing its z value
0129                     id: newAreaSensor
0130                     onHoveredChanged: if (hovered) { // onEntered the area
0131                         if (selection.state === "queueNewArea") selection.state = "addingNewArea"
0132                     }
0133                 }
0134             }
0135 
0136             property point dragStart
0137             property point dragEnd
0138 
0139             states: [
0140                 State {
0141                     name: "idle"
0142                     when: !selection.pressed
0143                     PropertyChanges {
0144                         restoreEntryValues: false
0145                         target: selection
0146                         selectionX: skanpage.scanArea.x * selection.width
0147                         selectionY: skanpage.scanArea.y * selection.height
0148                         selectionWidth: skanpage.scanArea.width * selection.width
0149                         selectionHeight: skanpage.scanArea.height * selection.height
0150                     }
0151                 },
0152                 State {
0153                     name: ""
0154                     PropertyChanges {
0155                         explicit: true
0156                         target: selection
0157                         selectionX: undefined // Manual control
0158                         selectionY: undefined // Manual control
0159                         selectionWidth: undefined // Manual control
0160                         selectionHeight: undefined // Manual control
0161                     }
0162                 },
0163                 State { // Only used for the transition, especially if the mouse is outside the image
0164                     name: "queueNewArea"
0165                     extend: "idle"
0166                 },
0167                 State {
0168                     name: "addingNewArea"
0169                     PropertyChanges {
0170                         restoreEntryValues: false
0171                         target: selection
0172                         selectionX: newAreaSensor.point.position.x
0173                         selectionY: newAreaSensor.point.position.y
0174                         selectionWidth: Kirigami.Units.gridUnit
0175                         selectionHeight: Kirigami.Units.gridUnit
0176                     }
0177                 },
0178                 State {
0179                     name: "sizingNewArea"
0180                     PropertyChanges {
0181                         explicit: true
0182                         target: selection
0183                         dragStart: Qt.point(selectionX, selectionY)
0184                     }
0185                     PropertyChanges {
0186                         restoreEntryValues: false
0187                         target: selection
0188                         dragEnd: Qt.point(Math.min(Math.max(newAreaSelector.mouseX, 0), selection.width),
0189                                           Math.min(Math.max(newAreaSelector.mouseY, 0), selection.height))
0190                         selectionX: dragEnd.x > dragStart.x ? dragStart.x : dragEnd.x
0191                         selectionY: dragEnd.y > dragStart.y ? dragStart.y : dragEnd.y
0192                         selectionWidth:  Math.max(Math.abs(dragEnd.x - dragStart.x), Kirigami.Units.gridUnit)
0193                         selectionHeight: Math.max(Math.abs(dragEnd.y - dragStart.y), Kirigami.Units.gridUnit)
0194                     }
0195                 }
0196             ]
0197 
0198             KQuickImageEditor.CropBackground {
0199                 anchors.fill: parent
0200                 insideX: selection.selectionX
0201                 insideY: selection.selectionY
0202                 insideWidth: selection.selectionWidth
0203                 insideHeight: selection.selectionHeight
0204             }
0205 
0206             Rectangle {
0207                 x: selection.selectionX + selection.selectionWidth / 2 - 1
0208                 y: selection.selectionY
0209                 width: 2
0210                 height: selection.selectionHeight
0211                 color: Kirigami.Theme.highlightColor
0212                 visible: skanpage.scanSplit === Skanpage.ScanIsSplitV
0213             }
0214 
0215             Rectangle {
0216                 x: selection.selectionX
0217                 y: selection.selectionY + selection.selectionHeight / 2 - 1
0218                 width: selection.selectionWidth
0219                 height: 2
0220                 color: Kirigami.Theme.highlightColor
0221                 visible: skanpage.scanSplit === Skanpage.ScanIsSplitH
0222             }
0223 
0224             Repeater {
0225                 z: 1
0226                 model: skanpage.scanSubAreas
0227                 delegate: Rectangle {
0228                     id: subAreaGraphic
0229                     z: 1
0230                     color: "transparent"
0231                     border.color: Kirigami.Theme.focusColor
0232                     border.width: 2
0233                     x: modelData.x * selection.width
0234                     y: modelData.y * selection.height
0235                     width: modelData.width * selection.width
0236                     height: modelData.height * selection.height
0237 
0238                     MouseArea {
0239                         z: -1 // Give priority to the main area
0240                         parent: selection // To make z work
0241                         x: subAreaGraphic.x; width:  subAreaGraphic.width
0242                         y: subAreaGraphic.y; height: subAreaGraphic.height
0243                         onClicked: skanpage.selectSubArea(index)
0244                     }
0245                     TransparentButton {
0246                         anchors {
0247                             top:   parent.top; right: parent.right
0248                             topMargin:   Math.min(Math.max(
0249                                 parent.height - implicitHeight, 0), Kirigami.Units.smallSpacing)
0250                             rightMargin: Math.min(Math.max(
0251                                 parent.width/2 - implicitWidth, 0), Kirigami.Units.smallSpacing)
0252                         }
0253                         width: anchors.rightMargin === 0 ? parent.width / 2 : implicitWidth
0254                         height: anchors.topMargin  === 0 ? parent.height    : implicitHeight
0255                         z: 1
0256                         icon.name: anchors.rightMargin === 0 || anchors.topMargin === 0 ? "" : "edit-delete-remove"
0257                         onClicked: skanpage.eraseSubArea(index)
0258                         ToolTip.text: i18n("Discard this selection")
0259                     }
0260                 }
0261             }
0262         }
0263     }
0264 
0265     Kirigami.ActionToolBar {
0266         id: selectionToolbar
0267         visible: previewImage.visible
0268 
0269         anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
0270         height: Kirigami.Units.gridUnit * 2
0271 
0272         alignment: Qt.AlignCenter
0273 
0274         actions: [
0275             ShortcutsAction {
0276                 id: addSubAreaAction
0277                 icon.name: "document-open-recent"
0278                 text: i18n("Add Selection Area")
0279                 shortcut: "Q"
0280                 enabled: previewImage.visible
0281                 onTriggered: {
0282                     skanpage.newUserMessage(SkanpageUtils.InformationMessage,
0283                                             i18n("Click and drag to select another area"))
0284                     skanpage.appendSubArea(getScanArea())
0285                     selection.state = "queueNewArea"
0286                     if (newAreaSensor.hovered) selection.state = "addingNewArea"
0287                 }
0288             },
0289 
0290             ShortcutsAction {
0291                 id: splitVerticalAction
0292                 icon.name: "view-split-left-right"
0293                 text: i18n("Split Scan Vertically")
0294                 shortcut: "V"
0295                 enabled: previewImage.visible
0296                 checkable: true
0297                 checked: skanpage.scanSplit === Skanpage.ScanIsSplitV
0298                 onTriggered: skanpage.scanSplit = checked ? Skanpage.ScanIsSplitV : Skanpage.ScanNotSplit
0299             },
0300 
0301             ShortcutsAction {
0302                 id: splitHorizontalAction
0303                 icon.name: "view-split-top-bottom"
0304                 text: i18n("Split Scan Horizontally")
0305                 shortcut: "H"
0306                 enabled: previewImage.visible
0307                 checkable: true
0308                 checked: skanpage.scanSplit === Skanpage.ScanIsSplitH
0309                 onTriggered: skanpage.scanSplit = checked ? Skanpage.ScanIsSplitH : Skanpage.ScanNotSplit
0310             }
0311         ]
0312     }
0313 
0314     ColumnLayout {
0315         id: documentLayout
0316 
0317         anchors.fill: parent
0318 
0319         spacing: 0
0320 
0321         visible: skanpage.documentModel.count > 0 && (!showPreview || previewImage.null)
0322 
0323         ScrollView {
0324             id: imageViewer
0325             Layout.fillWidth: true
0326             Layout.fillHeight: true
0327 
0328             contentWidth: Math.max(bigImage.parent.width, imageViewer.availableWidth)
0329             contentHeight: Math.max(bigImage.parent.height, imageViewer.availableHeight)
0330 
0331             Item {
0332                 anchors.centerIn: parent
0333 
0334                 implicitWidth: bigImage.landscape ? bigImage.height : bigImage.width
0335                 implicitHeight: bigImage.landscape ? bigImage.width : bigImage.height
0336 
0337                 Image {
0338                     id: bigImage
0339 
0340                     readonly property bool landscape: rotation === 270 || rotation === 90
0341                     property double zoomScale: Math.min(imageViewer.availableWidth / bigImage.sourceSize.width, 1)
0342 
0343                     anchors {
0344                         horizontalCenter: parent.horizontalCenter
0345                         verticalCenter: parent.verticalCenter
0346                     }
0347 
0348                     width: sourceSize.width * zoomScale
0349                     height: sourceSize.height * zoomScale
0350 
0351                     source: skanpage.documentModel.activePageSource
0352 
0353                     rotation: skanpage.documentModel.activePageRotation
0354                     transformOrigin: Item.Center
0355                 }
0356             }
0357 
0358             TransparentButton {
0359                 parent: imageViewer
0360                 anchors.top: parent.top
0361                 anchors.right: parent.ScrollBar.vertical.visible ? parent.ScrollBar.vertical.left : parent.right
0362                 action: showPreviewAction
0363             }
0364         }
0365 
0366         Kirigami.ActionToolBar {
0367             Layout.fillWidth: true
0368             Layout.preferredHeight: Kirigami.Units.gridUnit * 2
0369 
0370             alignment: Qt.AlignLeft
0371 
0372             actions: [
0373                 ShortcutsAction {
0374                     id: zoomInAction
0375                     icon.name: "zoom-in"
0376                     text: i18n("Zoom In")
0377                     shortcutsName: "ZoomIn"
0378                     onTriggered: bigImage.zoomScale *= 1.5
0379                     enabled: bigImage.zoomScale < 8
0380                 },
0381 
0382                 ShortcutsAction {
0383                     id: zoomOutAction
0384                     icon.name: "zoom-out"
0385                     text: i18n("Zoom Out")
0386                     shortcutsName: "ZoomOut"
0387                     onTriggered: bigImage.zoomScale *= 0.75
0388                     enabled: bigImage.width > imageViewer.availableWidth / 2
0389                 },
0390 
0391                 ShortcutsAction {
0392                     id: zoomFitAction
0393                     icon.name: "zoom-fit-best"
0394                     text: i18n("Zoom Fit")
0395                     shortcut: "A"
0396                     onTriggered: {
0397                         var zoomScaleWidth = imageViewer.availableWidth / bigImage.sourceSize.width
0398                         var zoomScaleHeight = imageViewer.availableHeight / bigImage.sourceSize.height
0399                         if (zoomScaleWidth < zoomScaleHeight) {
0400                             bigImage.zoomScale = zoomScaleWidth
0401                         } else {
0402                             bigImage.zoomScale = zoomScaleHeight
0403                         }
0404                     }
0405                 },
0406 
0407                 ShortcutsAction {
0408                     id: zoomOrigAction
0409                     icon.name: "zoom-original"
0410                     text: i18n("Zoom 100%")
0411                     shortcut: "F"
0412                     onTriggered: bigImage.zoomScale = 1
0413                 },
0414 
0415                 ShortcutsAction {
0416                     id: rotateLeftAction
0417                     icon.name: "object-rotate-left"
0418                     text: i18n("Rotate Left")
0419                     onTriggered: skanpage.documentModel.rotateImage(skanpage.documentModel.activePageIndex, DocumentModel.Rotate90negative)
0420                 },
0421 
0422                 ShortcutsAction {
0423                     id: rotateRightAction
0424                     icon.name: "object-rotate-right"
0425                     text: i18n("Rotate Right")
0426                     onTriggered: skanpage.documentModel.rotateImage(skanpage.documentModel.activePageIndex, DocumentModel.Rotate90positive)
0427                 },
0428 
0429                 ShortcutsAction {
0430                     id: flipVerticalAction
0431                     icon.name: "object-flip-vertical"
0432                     text: i18n("Flip")
0433                     onTriggered: skanpage.documentModel.rotateImage(skanpage.documentModel.activePageIndex, DocumentModel.Flip180)
0434                 },
0435 
0436                 ShortcutsAction {
0437                     id: savePageAction
0438                     icon.name: "document-save"
0439                     text: i18n("Save Page")
0440                     onTriggered: documentPage.saveSinglePage(skanpage.documentModel.activePageIndex)
0441                 },
0442 
0443                 ShortcutsAction {
0444                     id: deleteAction
0445                     icon.name: "delete"
0446                     text: i18n("Delete Page")
0447                     shortcut: StandardKey.Delete
0448                     onTriggered: skanpage.documentModel.removeImage(skanpage.documentModel.activePageIndex)
0449                 }
0450             ]
0451         }
0452     }
0453 }
0454 
0455