Warning, /graphics/peruse/src/app/qml/viewers/ImageBrowser.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 * Copyright (C) 2015 Vishesh Handa <vhanda@kde.org> 0003 * 0004 * This library is free software; you can redistribute it and/or 0005 * modify it under the terms of the GNU Lesser General Public 0006 * License as published by the Free Software Foundation; either 0007 * version 2.1 of the License, or (at your option) version 3, or any 0008 * later version accepted by the membership of KDE e.V. (or its 0009 * successor approved by the membership of KDE e.V.), which shall 0010 * act as a proxy defined in Section 6 of version 3 of the license. 0011 * 0012 * This library is distributed in the hope that it will be useful, 0013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0015 * Lesser General Public License for more details. 0016 * 0017 * You should have received a copy of the GNU Lesser General Public 0018 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 0019 * 0020 */ 0021 0022 import QtQuick 2.12 0023 import QtQuick.Layouts 1.12 0024 import QtQuick.Window 2.12 0025 0026 import org.kde.kirigami 2.13 as Kirigami 0027 0028 import org.kde.peruse 0.1 as Peruse 0029 import "helpers" as Helpers 0030 0031 /** 0032 * @brief The image viewer used by the CBR and Folder Viewer Base classes. 0033 * 0034 * It handles drawing the image and the different zoom modes. 0035 */ 0036 ListView { 0037 id: root 0038 function goNextFrame() { root.currentItem.goNextFrame(); } 0039 function goPreviousFrame() { root.currentItem.goPreviousFrame(); } 0040 signal goNextPage(); 0041 signal goPreviousPage(); 0042 signal goPage(int pageNumber); 0043 signal activateExternalLink(string link); 0044 0045 onWidthChanged: restorationTimer.start() 0046 onHeightChanged: restorationTimer.start() 0047 Timer { 0048 id: restorationTimer 0049 interval: 300 0050 running: false 0051 repeat: false 0052 onTriggered: { 0053 if (currentItem) { 0054 imageBrowser.positionViewAtIndex(imageBrowser.currentIndex, ListView.Center); 0055 currentItem.refocusFrame(); 0056 } 0057 else { 0058 restorationTimer.start(); 0059 } 0060 } 0061 } 0062 0063 function navigateTo(pageNo, frameNo = -1) { 0064 goPage(pageNo); 0065 root.currentItem.currentFrame = frameNo; 0066 } 0067 0068 property string hoveredLink 0069 function handleLink(link) { 0070 var lowerLink = link.toLowerCase(); 0071 if (link.startsWith("#")) { 0072 // Then it's one of our internally identified objects with an explicit name 0073 var linkedObject = root.identifiedObjects.objectById(link.slice(1)); 0074 if (linkedObject) { 0075 if (linkedObject.objectType === "Reference" || linkedObject.objectType === "Binary") { 0076 // show a popup with the reference or binary in 0077 // if we've got a sensible way of showing it: 0078 // pdf 0079 // various image formats 0080 infoDisplay.showInfo(linkedObject); 0081 } else if (linkedObject.objectType === "Page") { 0082 // navigate to this page 0083 root.navigateTo(linkedObject.localIndex); 0084 } else if (linkedObject.objectType === "Frame") { 0085 // navigate to this frame/page combo 0086 root.navigateTo(linkedObject.parent.localIndex, linkedObject.localIndex); 0087 } else { 0088 // sorry, dunno what to do with these yet, let's tell the user 0089 applicationWindow().showPassiveNotification(i18n("The link you just activated goes to something we don't really know what to do with. This suggests the book you are reading does not conform to the Advanced Comic Book Format specification, but we would still like to know about it. Please report a bug, and make the book available to us, so we can try and work out how to handle this!")); 0090 } 0091 } else { 0092 // we didn't find an object with that name, so let's inform the user about that 0093 applicationWindow().showPassiveNotification(i18n("The link you activated doesn't seem to go anywhere. We tried to find something with the ID %1 but didn't locate anything. This is likely an error in the book, and the author would probably like to know about it.", link)); 0094 } 0095 } else if (lowerLink.startsWith("http://") || lowerLink.startsWith("https://") || lowerLink.startsWith("mailto:") || lowerLink.startsWith("file://")) { 0096 // This is an external link (relative to the archive), let's try and support those in a graceful manner 0097 // Be safe, show the link before launching it 0098 root.activateExternalLink(link); 0099 } else if (lowerLink.startsWith("zip:")) { 0100 // This is a bit of an oddity - link to a file in a zip archive 0101 // link format for e.g.: zip:path/to/file.zip!/path/to/file/page1.jpg 0102 // zip location can be either relative or absolute, so we need to deal with that... 0103 } else { 0104 // If none of the above match, assume we have a link to some internal archive 0105 // file, so let's see if the thing it's trying to link to actually exists... 0106 } 0107 } 0108 0109 function switchToNextJump() { 0110 root.currentItem.nextJump(); 0111 } 0112 0113 function activateCurrentJump() { 0114 root.currentItem.activateCurrentJump(); 0115 } 0116 0117 interactive: false // No interactive flicky stuff here, we'll handle that with the navigator instance 0118 property int imageWidth 0119 property int imageHeight 0120 0121 orientation: ListView.Horizontal 0122 snapMode: ListView.SnapOneItem 0123 cacheBuffer: 3000 0124 0125 readonly property QtObject identifiedObjects: Peruse.IdentifiedObjectModel { 0126 document: root.model.acbfData 0127 } 0128 0129 // This ensures that the current index is always up to date, which we need to ensure we can track the current page 0130 // as required by the thumbnail navigator, and the resume-reading-from functionality 0131 onMovementEnded: { 0132 var indexHere = indexAt(contentX + width / 2, contentY + height / 2); 0133 if(currentIndex !== indexHere) { 0134 currentIndex = indexHere; 0135 } 0136 } 0137 /** 0138 * An interactive area with an image. 0139 * 0140 * Clicking once on the image will hide all other controls from view. 0141 * Clicking twice will instead zoom in. 0142 * 0143 * Pinch will zoom in as well. 0144 */ 0145 delegate: Flickable { 0146 id: flick 0147 opacity: ListView.isCurrentItem ? 1 : 0 0148 Behavior on opacity { NumberAnimation { duration: applicationWindow().animationDuration; easing.type: Easing.InOutQuad; } } 0149 width: imageWidth 0150 height: imageHeight 0151 contentWidth: imageWidth 0152 contentHeight: imageHeight 0153 interactive: (contentWidth > width || contentHeight > height) && (totalFrames === 0) 0154 z: interactive ? 1000 : 0 0155 property bool hasInteractiveObjects: image.frameJumps.length > 0 || image.frameLinkRects.length > 0; 0156 function goNextFrame() { image.nextFrame(); } 0157 function goPreviousFrame() { image.previousFrame(); } 0158 function setColouredHole(holeRect,holeColor) { 0159 pageHole.setHole(holeRect); 0160 pageHole.color = holeColor; 0161 } 0162 Timer { 0163 id: refocusTimer 0164 interval: 200 0165 running: false 0166 repeat: false 0167 onTriggered: { 0168 flick.resetHole(); 0169 if (totalFrames > 0 && currentFrame > -1) { 0170 image.focusOnFrame(); 0171 } 0172 } 0173 } 0174 function refocusFrame() { 0175 refocusTimer.start(); 0176 } 0177 function resetHole() { 0178 if(image.status == Image.Ready) { 0179 var holeColor = "transparent"; 0180 if (image.currentPageObject !== null) { 0181 holeColor = image.currentPageObject.bgcolor; 0182 } 0183 setColouredHole(image.paintedRect, holeColor); 0184 } 0185 } 0186 function nextJump() { 0187 image.nextJump(); 0188 } 0189 function activateCurrentJump() { 0190 image.activateCurrentJump(); 0191 } 0192 ListView.onIsCurrentItemChanged: resetHole(); 0193 Connections { 0194 target: image 0195 function onStatusChanged() { refocusFrame(); } 0196 } 0197 property alias totalFrames: image.totalFrames; 0198 property alias currentFrame: image.currentFrame; 0199 pixelAligned: true 0200 property bool actuallyMoving: moving || xMover.running || yMover.running || widthMover.running || heightMover.running 0201 Behavior on contentX { NumberAnimation { id: xMover; duration: applicationWindow().animationDuration; easing.type: Easing.InOutQuad; } } 0202 Behavior on contentY { NumberAnimation { id: yMover; duration: applicationWindow().animationDuration; easing.type: Easing.InOutQuad; } } 0203 Behavior on contentWidth { NumberAnimation { id: widthMover; duration: applicationWindow().animationDuration; easing.type: Easing.InOutQuad; } } 0204 Behavior on contentHeight { NumberAnimation { id: heightMover; duration: applicationWindow().animationDuration; easing.type: Easing.InOutQuad; } } 0205 PinchArea { 0206 width: Math.max(flick.contentWidth, flick.width) 0207 height: Math.max(flick.contentHeight, flick.height) 0208 0209 property real initialWidth 0210 property real initialHeight 0211 0212 onPinchStarted: { 0213 initialWidth = flick.contentWidth 0214 initialHeight = flick.contentHeight 0215 } 0216 0217 onPinchUpdated: { 0218 // adjust content pos due to drag 0219 flick.contentX += pinch.previousCenter.x - pinch.center.x 0220 flick.contentY += pinch.previousCenter.y - pinch.center.y 0221 0222 // resize content 0223 flick.resizeContent(Math.max(imageWidth, initialWidth * pinch.scale), Math.max(imageHeight, initialHeight * pinch.scale), pinch.center) 0224 } 0225 0226 onPinchFinished: { 0227 // Move its content within bounds. 0228 flick.returnToBounds(); 0229 } 0230 0231 0232 Image { 0233 id: image 0234 width: flick.contentWidth 0235 height: flick.contentHeight 0236 Helpers.HolyRectangle { 0237 id: pageHole 0238 anchors.fill: parent 0239 color: image.currentFrameObj.bgcolor 0240 visible: opacity > 0 0241 opacity: image.currentFrame === -1 ? 1 : 0 0242 animatePosition: false 0243 } 0244 source: model.url 0245 fillMode: Image.PreserveAspectFit 0246 asynchronous: true 0247 property bool shouldCheat: imageWidth * 2 > maxTextureSize || imageHeight * 2 > maxTextureSize; 0248 property bool isTall: imageHeight < imageWidth; 0249 property int fixedWidth: isTall ? maxTextureSize * (imageWidth / imageHeight) : maxTextureSize; 0250 property int fixedHeight: isTall ? maxTextureSize : maxTextureSize * (imageHeight / imageWidth); 0251 sourceSize.width: shouldCheat ? fixedWidth : imageWidth * 2; 0252 sourceSize.height: shouldCheat ? fixedHeight : imageHeight * 2; 0253 MouseArea { 0254 anchors.fill: parent 0255 } 0256 0257 // Setup for all the entries. 0258 property QtObject currentPageObject: { 0259 if (root.model.acbfData) { 0260 if (model.index===0) { 0261 currentPageObject = root.model.acbfData.metaData.bookInfo.coverpage(); 0262 } else if (model.index > 0) { 0263 currentPageObject = root.model.acbfData.body.page(model.index-1); 0264 } 0265 } else { 0266 null; 0267 } 0268 } 0269 property real muliplier: isTall? (paintedHeight / pixHeight): (paintedWidth / pixWidth); 0270 property int offsetX: (width-paintedWidth)/2; 0271 property int offsetY: (height-paintedHeight)/2; 0272 property rect paintedRect: Qt.rect(offsetX, offsetY, paintedWidth, paintedHeight); 0273 0274 // This is some magic that QML Image does for us, to be helpful. It isn't very helpful to us. 0275 property int pixWidth: image.implicitWidth * Screen.devicePixelRatio; 0276 property int pixHeight: image.implicitHeight * Screen.devicePixelRatio; 0277 0278 function focusOnFrame() { 0279 flick.resizeContent(imageWidth, imageHeight, Qt.point(flick.contentX, flick.contentY)); 0280 var frameObj = image.currentFrameObj; 0281 var frameBounds = frameObj.bounds; 0282 var frameMultiplier = image.pixWidth/frameBounds.width * (root.imageWidth/image.paintedWidth); 0283 // If it's now too large to fit inside the viewport, scale it by height instead 0284 if ((frameBounds.height/frameBounds.width)*root.imageWidth > root.imageHeight) { 0285 frameMultiplier = image.pixHeight/frameBounds.height * (root.imageHeight/image.paintedHeight); 0286 } 0287 // console.debug("Frame bounds for frame " + index + " are " + frameBounds + " with multiplier " + frameMultiplier); 0288 // console.debug("Actual pixel size of image with implicit size " + image.implicitWidth + " by " + image.implicitHeight + " is " + pixWidth + " by " + pixHeight); 0289 flick.resizeContent(imageWidth * frameMultiplier, imageHeight * frameMultiplier, Qt.point(flick.contentX,flick.contentY)); 0290 var frameRect = Qt.rect((image.muliplier * frameBounds.x) + image.offsetX 0291 , (image.muliplier * frameBounds.y) + image.offsetY 0292 , (image.muliplier * frameBounds.width) 0293 , (image.muliplier * frameBounds.height)); 0294 flick.contentX = frameRect.x - (flick.width-frameRect.width)/2; 0295 flick.contentY = frameRect.y - (flick.height-frameRect.height)/2; 0296 } 0297 0298 function nextFrame() { 0299 if (image.totalFrames > 0 && image.currentFrame+1 < image.totalFrames) { 0300 image.currentFrame++; 0301 } else { 0302 image.currentFrame = -1; 0303 flick.returnToBounds(); 0304 root.goNextPage(); 0305 if(root.currentItem.totalFrames > 0) { 0306 root.currentItem.currentFrame = 0; 0307 } 0308 } 0309 } 0310 0311 function previousFrame() { 0312 if (image.totalFrames > 0 && image.currentFrame-1 > -1) { 0313 image.currentFrame--; 0314 } else { 0315 image.currentFrame = -1; 0316 flick.returnToBounds(); 0317 root.goPreviousPage(); 0318 if(root.currentItem.totalFrames > 0) { 0319 root.currentItem.currentFrame = root.currentItem.totalFrames - 1; 0320 } 0321 } 0322 } 0323 0324 property int totalFrames: image.currentPageObject? image.currentPageObject.framePointStrings.length: 0; 0325 property int currentFrame: -1; 0326 property QtObject currentFrameObj: image.currentPageObject && image.totalFrames > 0 && image.currentFrame > -1 ? image.currentPageObject.frame(currentFrame) : noFrame; 0327 onCurrentFrameObjChanged: { 0328 initFrame(); 0329 focusOnFrame(image.currentFrame); 0330 } 0331 property QtObject noFrame: QtObject { 0332 property rect bounds: image.paintedRect 0333 property color bgcolor: image.currentPageObject? image.currentPageObject.bgcolor: "transparent"; 0334 } 0335 0336 // if we're on touch screen, we set the currentJumpIndex to -1 by default 0337 // otherwise we set it to the first available jump on the frame 0338 property int currentJumpIndex: Kirigami.Settings.isMobile? -1 : 0; 0339 property var frameJumps: []; 0340 0341 function initFrame() { 0342 currentJumpIndex = Kirigami.Settings.isMobile? -1 : 0; 0343 var newFrameJumps = []; 0344 0345 if(currentFrameObj === noFrame) { 0346 newFrameJumps = image.currentPageObject? image.currentPageObject.jumps : []; 0347 } else { 0348 for(var i = 0; i < image.currentPageObject.jumps.length; i++) { 0349 var jumpObj = image.currentPageObject.jump(i); 0350 if(frameContainsJump(jumpObj)) { 0351 newFrameJumps.push(jumpObj); 0352 } 0353 } 0354 } 0355 frameJumps = newFrameJumps; 0356 updateFrameLinkRects(); 0357 } 0358 0359 property var frameLinkRects: []; 0360 function updateFrameLinkRects() { 0361 var newLinkRects = []; 0362 for(var i = 0; i < textAreaRepeater.model.length; i++) { 0363 if (frameContainsJump(textAreaRepeater.model[i])) { 0364 for(var j = 0; j < textAreaRepeater.count; j++) { 0365 newLinkRects.push(textAreaRepeater.itemAt(j).linkRects[j]); 0366 } 0367 } 0368 } 0369 frameLinkRects = newLinkRects; 0370 } 0371 0372 Repeater { 0373 id: textAreaRepeater 0374 property QtObject textLayer: root.currentLanguage ? image.currentPageObject.textLayer(root.currentLanguage.language) : null 0375 onTextLayerChanged: { image.updateFrameLinkRects(); } 0376 model: textLayer ? textLayer.textareas : 0; 0377 Helpers.TextAreaHandler { 0378 id: textAreaHandler 0379 model: root.model 0380 identifiedObjects: root.identifiedObjects 0381 multiplier: image.muliplier 0382 offsetX: image.offsetX 0383 offsetY: image.offsetY 0384 textArea: modelData 0385 enabled: image.frameContainsJump(modelData) && !flick.actuallyMoving 0386 onLinkActivated: { root.handleLink(link); } 0387 onHoveredLinkChanged: { root.hoveredLink = textAreaHandler.hoveredLink; } 0388 // for mobile, make tooltip a tap instead (or tap-and-hold), and tap-to-dismiss 0389 // hover on bin link, show name in tooltip, if image show thumbnail in tooltop, on click open in popup if we know how, offer external if we don't (maybe in that popup?) 0390 // hover on ref link, show small snippet in tooltip, on click open first in popup, if already in popup, open in full display reader 0391 // hover on page(/frame), tooltip shows destination in some pleasant format (if page has non-numeric title, show the title) 0392 // hover on external link, show something about opening external link 0393 } 0394 } 0395 0396 /** 0397 * \brief returns true if the sent jump bounds are within the image's current frame 0398 * @param jumpObj - the given jump object 0399 */ 0400 function frameContainsJump(jumpObj) { 0401 if(flick.ListView.isCurrentItem) { 0402 if(image.currentFrameObj === noFrame) { 0403 return true; 0404 } 0405 0406 return jumpObj.bounds.x >= image.currentFrameObj.bounds.x && 0407 jumpObj.bounds.y >= image.currentFrameObj.bounds.y && 0408 (jumpObj.bounds.x + jumpObj.bounds.width) <= (image.currentFrameObj.bounds.x + image.currentFrameObj.bounds.width) && 0409 (jumpObj.bounds.y + jumpObj.bounds.height) <= (image.currentFrameObj.bounds.y + image.currentFrameObj.bounds.height); 0410 } 0411 0412 return false; 0413 } 0414 0415 function nextJump() { 0416 if(image.currentJumpIndex === -1 || !jumpsRepeater.itemAt(image.currentJumpIndex).hovered) { 0417 image.currentJumpIndex = (image.currentJumpIndex + 1) % image.frameJumps.length; 0418 } 0419 //image.currentJumpIndex = image.currentJumpIndex === image.frameJumps.length - 1? 0 : ++image.currentJumpIndex; 0420 } 0421 0422 function activateCurrentJump() { 0423 if(currentJumpIndex !== -1 && jumpsRepeater.itemAt(currentJumpIndex)) { 0424 jumpsRepeater.itemAt(currentJumpIndex).activated(); 0425 } 0426 } 0427 0428 Repeater { 0429 id: jumpsRepeater; 0430 model: image.frameJumps; 0431 0432 Helpers.JumpHandler { 0433 jumpObject: modelData; 0434 0435 offsetX: image.offsetX; 0436 offsetY: image.offsetY; 0437 0438 widthMultiplier: image.muliplier; 0439 heightMultiplier: image.muliplier; 0440 0441 focused: image.currentJumpIndex === index; 0442 0443 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.InOutQuad; } } 0444 0445 onActivated: { 0446 // href for jumps is fairly new, so we need to fall back gracefully 0447 // (also this allows jumps to link to unnamed pages) 0448 if (jumpObject.href.length > 0) { 0449 root.handleLink(jumpObject.href); 0450 } else { 0451 root.navigateTo(jumpObject.pageIndex); 0452 } 0453 } 0454 0455 onHoveredChanged: image.currentJumpIndex = (hovered ? index : -1); 0456 } 0457 } 0458 0459 Repeater { 0460 model: image.currentPageObject? image.currentPageObject.framePointStrings: 0; 0461 // Rectangle { 0462 // id: frame; 0463 // x: (image.muliplier * image.currentPageObject.frame(index).bounds.x) + image.offsetX; 0464 // y: (image.muliplier * image.currentPageObject.frame(index).bounds.y) + image.offsetY; 0465 // width: image.muliplier * image.currentPageObject.frame(index).bounds.width; 0466 // height: image.muliplier * image.currentPageObject.frame(index).bounds.height; 0467 // color: "blue"; 0468 // opacity: 0; 0469 // } 0470 Helpers.HolyRectangle { 0471 anchors.fill: parent; 0472 property QtObject frameObj: image.currentPageObject ? image.currentPageObject.frame(index) : noFrame; 0473 property rect frameRect: Qt.rect((image.muliplier * frameObj.bounds.x) + image.offsetX, 0474 (image.muliplier * frameObj.bounds.y) + image.offsetY, 0475 (image.muliplier * frameObj.bounds.width), 0476 (image.muliplier * frameObj.bounds.height)) 0477 color: frameObj.bgcolor; 0478 opacity: image.currentFrame === index ? 1 : 0; 0479 visible: opacity > 0; 0480 topBorder: frameRect.y; 0481 leftBorder: frameRect.x; 0482 rightBorder: width - (frameRect.x + frameRect.width); 0483 bottomBorder: height - (frameRect.y + frameRect.height); 0484 } 0485 } 0486 0487 MouseArea { 0488 anchors.fill: parent; 0489 enabled: flick.interactive && !root.currentItem.hasInteractiveObjects; 0490 onClicked: startToggleControls(); 0491 onDoubleClicked: { 0492 abortToggleControls(); 0493 flick.resizeContent(imageWidth, imageHeight, Qt.point(imageWidth/2, imageHeight/2)); 0494 flick.returnToBounds(); 0495 } 0496 } 0497 Kirigami.PlaceholderMessage { 0498 anchors.centerIn: parent 0499 width: parent.width - (Kirigami.Units.largeSpacing * 4) 0500 visible: image.status === Image.Error; 0501 text: i18nc("Message shown on the book reader view when there is an issue loading the image for a specific page", "Could not load the image for this page.\nThis is most commonly due to a missing image decoder (specifically, the Qt Imageformats package, which Peruse depends on for loading images), and likely a packaging error. Contact whoever you got this package from and inform them of this error.\n\nSpecifically, the image we attempted to load is called %1, and the image formats Qt is aware of are %2. If there is a mismatch there, that will be the problem.\n\nIf not, please report this bug to us, and give as much information as you can to assist us in working out what's wrong.", image.source, peruseConfig.supportedImageFormats().join(", ")); 0502 } 0503 } 0504 } 0505 } 0506 0507 Kirigami.OverlaySheet { 0508 id: infoDisplay 0509 function showInfo(theObject) { 0510 infoDisplay.theObject = theObject; 0511 infoDisplay.open(); 0512 } 0513 property QtObject theObject 0514 showCloseButton: true 0515 header: Kirigami.Heading { 0516 text: infoDisplay.theObject ? infoDisplay.theObject.id : "" 0517 Layout.fillWidth: true 0518 elide: Text.ElideRight 0519 } 0520 contentItem: ColumnLayout { 0521 Layout.fillWidth: true 0522 Layout.preferredWidth: root.width * .8 0523 Text { 0524 opacity: infoDisplay.theObject && infoDisplay.theObject.objectType === "Reference" ? 1 : 0 0525 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration; } } 0526 Layout.fillWidth: true 0527 Layout.preferredWidth: root.width - Kirigami.Units.largeSpacing * 2 0528 textFormat: Text.StyledText 0529 wrapMode: Text.Wrap 0530 // We need some pleasant way to get the paragraphs and turn them into sensibly rich-text styled text (perhaps on Stylesheet? Throw it a qstringlist and it spits out a formatted html string with the styles etc?) 0531 text: opacity > 0 ? "<p>" + infoDisplay.theObject.paragraphs.join("</p><p>") + "</p>": "" 0532 onLinkActivated: { root.handleLink(link); } 0533 onLinkHovered: { 0534 // Show first line in a popup, or destination, etc, as for Textareas 0535 } 0536 } 0537 Image { 0538 opacity: infoDisplay.theObject && infoDisplay.theObject.objectType === "Binary" ? 1 : 0 0539 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration; } } 0540 fillMode: Image.PreserveAspectFit 0541 source: opacity > 0 ? root.model.previewForId("#" + infoDisplay.theObject.id) : "" 0542 } 0543 } 0544 } 0545 0546 Helpers.Navigator { 0547 enabled: root.currentItem ? !root.currentItem.interactive : false; 0548 acceptTaps: !root.currentItem.hasInteractiveObjects; 0549 anchors.fill: parent; 0550 onLeftRequested: root.layoutDirection === Qt.RightToLeft? root.goNextFrame(): root.goPreviousFrame(); 0551 onRightRequested: root.layoutDirection === Qt.RightToLeft? root.goPreviousFrame(): root.goNextFrame(); 0552 onTapped: startToggleControls(); 0553 onDoubleTapped: { 0554 abortToggleControls(); 0555 if (root.currentItem.totalFrames === 0) { 0556 root.currentItem.resizeContent(imageWidth * 2, imageHeight * 2, Qt.point(eventPoint.x, eventPoint.y)); 0557 root.currentItem.returnToBounds(); 0558 } 0559 } 0560 } 0561 }