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