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