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 }