I have made the following changes intended for : CE:Apps:MTF / qmlgallery
Please review and accept or decline. BOSS has already run some checks on this request. See the "Messages from BOSS" section below. https://build.pub.meego.com//request/show/5956 Thank You, John Brooks [This message was auto-generated] --- Request # 5956: Messages from BOSS: State: review at 2012-08-21T04:48:04 by bossbot Reviews: accepted by bossbot : Prechecks succeeded. new for CE-maintainers : Please replace this text with a review and approve/reject the review (not the SR). BOSS will take care of the rest Changes: submit: home:special:branches:CE:Apps:MTF / qmlgallery -> CE:Apps:MTF / qmlgallery changes files: -------------- --- qmlgallery.changes +++ qmlgallery.changes @@ -0,0 +1,10 @@ +* Mon Aug 20 2012 John Brooks <[email protected]> - 0.0.8 +- Fixes NEMO#191: No way to refresh gallery +- Fixes NEMO#238: Support formats other than JPEG +- Fixes NEMO#186: If no images are available tell user about it +- Fixes NEMO#231: Flipping orientation in full image view can change image +- Display name of the menu entry changed from qmlgallery to Gallery +- Fixes NEMO#192: Slideshow feature for gallery +- Fixes NEMO#252: Not possible to determine position in the gallery when scrolling +- Fixes NEMO#185 : Unable to show photos full screen + old: ---- qmlgallery-0.0.7.tar.bz2 new: ---- qmlgallery-0.0.8.tar.bz2 spec files: ----------- --- qmlgallery.spec +++ qmlgallery.spec @@ -1,6 +1,6 @@ # # Do NOT Edit the Auto-generated Part! -# Generated by: spectacle version 0.24.1 +# Generated by: spectacle version 0.25 # Name: qmlgallery @@ -9,7 +9,7 @@ # << macros Summary: Photo Gallery for Nemo -Version: 0.0.7 +Version: 0.0.8 Release: 1 Group: Applications/System License: BSD other changes: -------------- ++++++ qmlgallery-0.0.7.tar.bz2 -> qmlgallery-0.0.8.tar.bz2 --- qml/ImageContainer.qml +++ qml/ImageContainer.qml @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2012 Andrea Bernabei <[email protected]> + * + * You may use this file under the terms of the BSD license as follows: + * + * "Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Nemo Mobile nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + */ + +import QtQuick 1.1 +import com.nokia.meego 1.0 + +Item { + id: imgContainer + + property int index: -1 + width: imgController.imgContainerWidth + height: imgController.imgContainerHeight + + //This item has to be child of the controller + property variant imgController: parent + + function resetZoom() { + //resetting all variables related to pinch-to-zoom + img.scale = 1 + flickImg.contentX = flickImg.contentY = 0 + pinchImg.lastContentX = pinchImg.lastContentY = pinchImg.deltaX = pinchImg.deltaY = 0 + pinchImg.lastScaleX = pinchImg.lastScaleY = 1 + pinchImg.isZoomingOut = false + } + + PinchArea { + id: pinchImg + anchors.fill: imgContainer + + //Disable the pincharea if the listview is scrolling, to avoid problems + enabled: !imgController.moving + pinch.target: img + pinch.maximumScale: 5 + pinch.dragAxis: Pinch.NoDrag + + property real lastContentX: 0 + property real lastContentY: 0 + property real lastScaleX: 1 + property real lastScaleY: 1 + property real deltaX: 0 + property real deltaY: 0 + property bool initializedX: false + property bool initializedY: false + property bool isZoomingOut: false + + + function updateContentX() { + + //Only calculate the correct ContentX if the image is wider than the screen, otherwise keep it centered (contentX = 0 in the else branch) + if (rect.width == imgController.width) { + + //Anchors the image to the left + if (flickImg.contentX < 0){ + deltaX = 0.0 + lastContentX = 0.0 + flickImg.contentX = 0.0 + } + else { + //if the right end of the image is inside the screen area, lock it to the right and zoom out using right edge as an anchor + if ((flickImg.contentWidth - flickImg.contentX < parent.width) && isZoomingOut) { + + //align to the right + flickImg.contentX -= parent.width - (flickImg.contentWidth - flickImg.contentX) + + //Algo: set variable as if a new pinch starting from right edge were triggered + lastContentX = flickImg.contentX + deltaX = flickImg.contentX + parent.width + lastScaleX = img.scale + } + + flickImg.contentX = (lastContentX + deltaX * ((img.scale / lastScaleX) - 1.0 )) + } + } + else { + flickImg.contentX = 0 + } + } + + function updateContentY() { + + //Only calculate the correct ContentY if the image is taller than the screen, otherwise keep it centered (contentY = 0 in the else branch) + if (rect.height == imgController.height) { + + //Anchors the image to the top when zooming out + if (flickImg.contentY < 0) { + deltaY = 0.0 + lastContentY = 0.0 + flickImg.contentY = 0.0 + } + else { + //if the bottom end of the image is inside the screen area, lock it to the bottom and zoom out using bottom edge as an anchor + if ((flickImg.contentHeight - flickImg.contentY < parent.height) && isZoomingOut) { + //align to the bottom + flickImg.contentY -= parent.height - (flickImg.contentHeight - flickImg.contentY) + + //Algo: set variable as if a new pinch starting from bottom edge were triggered + lastContentY = flickImg.contentY + deltaY = flickImg.contentY + parent.height + lastScaleY = img.scale + } + flickImg.contentY = (lastContentY + deltaY * ((img.scale / lastScaleY) - 1.0 )) + } + } + else { + flickImg.contentY = 0 + } + } + + + onPinchUpdated: { + //Am I zooming in or out? + if (pinch.scale > pinch.previousScale) isZoomingOut = false + else isZoomingOut = true + + //Get updated "zoom center point" values when the image is completely zoomed out + if(img.scale == 1) { + //This is so that everytime you zoom out, the new zoom is started with updated values + initializedX = false + initializedY = false + } + + //i.e. everytime the image is wider than the screen, it should actually be + // img.width == imgController.width, but this condition is rarely met because of numeric error + if (rect.width == imgController.width) { + if (!initializedX ) { + //If it has not already been set by the "if (height == parent.imgController.height)" branch, set the scale here + lastScaleX = img.scale + + lastContentX = flickImg.contentX + deltaX = flickImg.contentX + pinch.center.x + initializedX = true; + } + + } + + if (rect.height == imgController.height) { + if (!initializedY) { + //If it has not already been set by the "if (width == imgController.width)", set the scale here + lastScaleY = img.scale + + lastContentY = flickImg.contentY + deltaY = flickImg.contentY + pinch.center.y + initializedY = true; + } + } + // updateContentX and updateContentY are called after the scale on the target item updates bindings + } + + onPinchFinished: { + lastContentX = flickImg.contentX + lastContentY = flickImg.contentY + + initializedX = false + initializedY = false + } + + } + + Item { + id: rect + anchors.centerIn: parent + + width: Math.min(img.width*img.scale, parent.width) + height: Math.min(img.height*img.scale, parent.height) + + Flickable { + id: flickImg + + anchors.fill: rect + transformOrigin: Item.TopLeft + + contentWidth: img.width * img.scale + contentHeight: img.height * img.scale + + onContentWidthChanged: { + pinchImg.updateContentX() + pinchImg.updateContentY() + } + onContentHeightChanged: { + pinchImg.updateContentX() + pinchImg.updateContentY() + } + + Image { + id: img + + //For Harmattan/Nemo ( THIS PART HAS TO BE FIXED , THE IMAGE IS NOT SCALED TO FILL THE SCREEN ATM) + property real imgRatio: galleryModel.get(index).width / galleryModel.get(index).height + property bool fitsVertically: imgRatio < (imgContainer.width / imgContainer.height) + width: (fitsVertically) ? (imgController.height * imgRatio) : imgController.width + height: (fitsVertically) ? (imgController.height) : (imgController.width / imgRatio) + + //For Simulator: + //width: 480 + //fillMode: Image.PreserveAspectFit + + transformOrigin: Item.TopLeft + asynchronous: true + source: galleryModel.get(index).url + sourceSize.width: 2000 + sourceSize.height: 2000 + + //Disable ListView scrolling if you're zooming + onScaleChanged: { + if (scale != 1 && imgController.flickAreaEnabled == true) imgController.flickAreaEnabled = false + else if (scale == 1 && imgController.flickAreaEnabled == false) imgController.flickAreaEnabled = true + } + + MouseArea { + anchors.fill: parent + + Timer { + id: doubleClickTimer + interval: 350 + } + + // onDoubleClicked seems broken on-device with all of the flickable/pincharea here + onClicked: { + if (doubleClickTimer.running) resetZoom() + else doubleClickTimer.start() + } + } + } + + } + + } + + +} --- qml/ImagePage.qml +++ qml/ImagePage.qml @@ -33,256 +33,209 @@ import com.nokia.meego 1.0 import QtMobility.gallery 1.1 -Page{ - id: imagepage - anchors.fill:parent +Page { + id: imageController + anchors.fill: parent + clip: true tools: imgTools - property alias imageId: imageview.currentIndex - property alias galleryModel: imageview.model + property int imgContainerWidth: width + property int imgContainerHeight: height + property variant galleryModel + // XXX: This is not actually the visible index; it's the index shown when + // loading the page. Use middle.index instead. Should be refactored. + property int visibleIndex: 0 + property real firstPressX + property real pressX + property int flickToX: 0 + property int flickFromX: 0 + property bool moving: false + property variant leftMost: one + property variant leftMiddle: two + property variant middle: three + property variant rightMiddle: four + property variant rightMost: five + property real swipeThreshold: 40 + property real leftMostOptimalX: -width*2 + //number of pixel you have to move before the Pinch Area is disabled + property real pinchThreshold: 3 + property alias flickAreaEnabled: imgFlickable.enabled + + onWidthChanged: { + middle.resetZoom() + alignToCenter() + } + + function modulus(a, b) { + if (a < 0) return (a+b) % b + else return a % b + } + + function alignToCenter() { + leftMost.x = leftMostOptimalX + } + + function swapLeftMost() { + leftMiddle.anchors.left = undefined + leftMost.anchors.left = rightMost.right + + //TODO: we could use a generic function instead of fixed assignments when shifting the positiong of the containers + //shift all elements left by one position, and make leftMost become rightMost + var oldLeftMost = leftMost + leftMost = leftMiddle + leftMiddle = middle + middle = rightMiddle + rightMiddle = rightMost + rightMost = oldLeftMost + //set the index (relative to galleryModel) of the image which has to be loaded by the shifted image container + rightMost.index = modulus(middle.index + 2, imageController.galleryModel.count); + } - ListView{ - id: imageview - anchors.fill:parent - clip: true - - orientation: ListView.Horizontal - highlightRangeMode: ListView.StrictlyEnforceRange - - //Set duration to 0 to skip listview animation when passing from grid to listview - highlightMoveDuration: 1 - highlightResizeDuration: 1 - - cacheBuffer:2*width - - snapMode: ListView.SnapOneItem - - delegate: Item{ - id: imgContainer - - width: imageview.width - height: imageview.height - - PinchArea{ - id: pinchImg - anchors.fill: imgContainer - - //Disable the pincharea if the listview is scrolling, to avoid problems - enabled: !imageview.moving - pinch.target: img - pinch.maximumScale: 5 - pinch.dragAxis: Pinch.NoDrag - - property real lastContentX: 0 - property real lastContentY: 0 - property real lastScaleX: 1 - property real lastScaleY: 1 - property real deltaX: 0 - property real deltaY: 0 - property bool initializedX: false - property bool initializedY: false - property bool isZoomingOut: false - - - function updateContentX(){ - - //Only calculate the correct ContentX if the image is wider than the screen, otherwise keep it centered (contentX = 0 in the else branch) - if (rect.width == imageview.width){ - - //Anchors the image to the left - if (flickImg.contentX < 0){ - deltaX = 0.0 - lastContentX = 0.0 - flickImg.contentX = 0.0 - } - else { - //if the right end of the image is inside the screen area, lock it to the right and zoom out using right edge as an anchor - if ((flickImg.contentWidth - flickImg.contentX < parent.width) && isZoomingOut){ - - //align to the right - flickImg.contentX -= parent.width - (flickImg.contentWidth - flickImg.contentX) - - //Algo: set variable as if a new pinch starting from right edge were triggered - lastContentX = flickImg.contentX - deltaX = flickImg.contentX + parent.width - lastScaleX = img.scale - } - - flickImg.contentX = (lastContentX + deltaX * ((img.scale / lastScaleX) - 1.0 )) - } - } - else{ - flickImg.contentX = 0 - } - } - - function updateContentY(){ - - //Only calculate the correct ContentY if the image is taller than the screen, otherwise keep it centered (contentY = 0 in the else branch) - if (rect.height == imageview.height){ - - //Anchors the image to the top when zooming out - if (flickImg.contentY < 0){ - deltaY = 0.0 - lastContentY = 0.0 - flickImg.contentY = 0.0 - } - else { - //if the bottom end of the image is inside the screen area, lock it to the bottom and zoom out using bottom edge as an anchor - if ((flickImg.contentHeight - flickImg.contentY < parent.height) && isZoomingOut){ - - //align to the bottom - flickImg.contentY -= parent.height - (flickImg.contentHeight - flickImg.contentY) - - //Algo: set variable as if a new pinch starting from bottom edge were triggered - lastContentY = flickImg.contentY - deltaY = flickImg.contentY + parent.height - lastScaleY = img.scale - } - flickImg.contentY = (lastContentY + deltaY * ((img.scale / lastScaleY) - 1.0 )) - } - } - else{ - flickImg.contentY = 0 - } - } - - - onPinchUpdated:{ - //Am I zooming in or out? - if (pinch.scale > pinch.previousScale) isZoomingOut = false - else isZoomingOut = true - - //Get updated "zoom center point" values when the image is completely zoomed out - if(img.scale == 1){ - //This is so that everytime you zoom out, the new zoom is started with updated values - initializedX = false - initializedY = false - } - - //i.e. everytime the image is wider than the screen, it should actually be - // img.width == imageview.width, but this condition is rarely met because of numeric error - if (rect.width == imageview.width) - { - if (!initializedX ) - { - //If it has not already been set by the "if (height == imageview.height)" branch, set the scale here - lastScaleX = img.scale - - lastContentX = flickImg.contentX - deltaX = flickImg.contentX + pinch.center.x - initializedX = true; - } - - } - - if (rect.height == imageview.height) - { - if (!initializedY) - { - //If it has not already been set by the "if (width == imageview.width)", set the scale here - lastScaleY = img.scale - - lastContentY = flickImg.contentY - deltaY = flickImg.contentY + pinch.center.y - initializedY = true; - } - - } - - // updateContentX and updateContentY are called after the scale on the target item updates bindings - } - - onPinchFinished: { - lastContentX = flickImg.contentX - lastContentY = flickImg.contentY - - initializedX = false - initializedY = false - } + function swapRightMost() { + rightMost.anchors.left = undefined + leftMost.anchors.left = rightMost.right + + //shift all elements right by one position, and make rightMost become leftMost + var oldRightMost = rightMost + rightMost = rightMiddle + rightMiddle = middle + middle = leftMiddle + leftMiddle = leftMost + leftMost = oldRightMost + leftMost.index = modulus(middle.index - 2, imageController.galleryModel.count); + } + NumberAnimation { + id: flickTo; + target: leftMost; + property: "x"; + from: flickFromX; + to: flickToX; + duration: 300; + easing.type: Easing.OutQuad + onCompleted: { + if (Math.abs(to - from) > swipeThreshold) { + if (from > to ) + swapLeftMost() + else + swapRightMost() + //center flickable view, this allows endless scrolling + alignToCenter() } - Item{ - id: rect - anchors.centerIn:parent - - width: Math.min(img.width*img.scale, parent.width) - height: Math.min(img.height*img.scale, parent.height) - - Flickable{ - id: flickImg - - anchors.fill:rect - transformOrigin: Item.TopLeft - - contentWidth: img.width * img.scale - contentHeight: img.height * img.scale - - onContentWidthChanged: { pinchImg.updateContentX(); pinchImg.updateContentY(); } - onContentHeightChanged: { pinchImg.updateContentX(); pinchImg.updateContentY(); } - - Image{ - id: img - - //For Harmattan/Nemo ( THIS PART HAS TO BE FIXED , THE IMAGE IS NOT SCALED TO FILL THE SCREEN ATM) - property real imgRatio: galleryModel.get(index).width / galleryModel.get(index).height - property bool fitsVertically: imgRatio < (imgContainer.width / imgContainer.height) - width: (fitsVertically) ? (imageview.height * imgRatio) : imageview.width - height: (fitsVertically) ? (imageview.height) : (imageview.width / imgRatio) - - //For Simulator: - //width: 480 - //fillMode: Image.PreserveAspectFit - - transformOrigin: Item.TopLeft - asynchronous: true - source: url - sourceSize.width: 2000 - sourceSize.height: 2000 - - //Disable ListView scrolling if you're zooming - onScaleChanged: { - if (scale != 1 && imageview.interactive == true) imageview.interactive = false - else if (scale == 1 && imageview.interactive == false) imageview.interactive = true - } - - MouseArea { - anchors.fill: parent - - Timer { - id: doubleClickTimer - interval: 350 - } - - // onDoubleClicked seems broken on-device with all of the flickable/pincharea here - onClicked: { - if (doubleClickTimer.running) { - img.scale = 1 - flickImg.contentX = flickImg.contentY = 0 - } - else - doubleClickTimer.start() - } - } - } + //This should be the only way the view can stop moving, so we set moving to false + moving = false + } + } + + //create items and position them in a row, + // ------ImageContainer must be child of the images controller ----- + ImageContainer { + id: one; + x: leftMostOptimalX; + index: modulus(visibleIndex - 2, galleryModel.count) + } + ImageContainer { + id: two + anchors.left: one.right + index: modulus(visibleIndex - 1, galleryModel.count) + } + ImageContainer { + id: three + anchors.left: two.right + index: visibleIndex + } + ImageContainer { + id: four + anchors.left: three.right + index: modulus (visibleIndex + 1, galleryModel.count) + } + ImageContainer { + id: five + anchors.left: four.right + index: modulus (visibleIndex + 2, galleryModel.count) + } - } + MouseArea { + id: imgFlickable + anchors.fill: parent + + property bool pressedForClick: false + + onPressed: { + firstPressX = mouseX + pressX = mouseX + pressedForClick = true + + //if the animation is running, make it stop and immediately slide to the image that you were going to + //this allows very fast scrolling + if (flickTo.running) flickTo.stop() + } + + onPositionChanged: { + if (Math.abs(firstPressX - mouseX) > pinchThreshold && moving == false) { + moving = true + pressedForClick = false } + //Only move the image if we're sure the user isn't trying to pinch + if (moving) { + leftMost.x = leftMost.x - (pressX - mouseX) + pressX = mouseX + } } + onReleased: { + if (middle.x >= swipeThreshold) { + //move it left + flickToX = leftMostOptimalX + imgContainerWidth + } + else if (middle.x <= -swipeThreshold) { + //move it right + flickToX = leftMostOptimalX -imgContainerWidth + } + else { + //bring it back + flickToX = leftMostOptimalX + } - } + if (pressedForClick) { + appWindow.fullscreen = !appWindow.fullscreen + pressedForClick = false + } + flickFromX = leftMost.x + flickTo.start() + } + } + Menu { + id: pageMenu + MenuLayout { + MenuItem { + text: "Slideshow" + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("ImageSlideshowPage.qml"), + {visibleIndex: imageController.middle.index, + galleryModel: imageController.galleryModel}, + true) + } + } + } ToolBarLayout { - id: imgTools - ToolIcon { - platformIconId: "toolbar-back" - anchors.left: (parent === undefined) ? undefined : parent.left - onClicked: appWindow.pageStack.pop() + id: imgTools + ToolIcon { + platformIconId: "toolbar-back" + anchors.left: (parent === undefined) ? undefined : parent.left + onClicked: { + appWindow.fullscreen = false + appWindow.pageStack.pop() } + } + ToolIcon { + platformIconId: "toolbar-view-menu" + anchors.right: (parent === undefined) ? undefined : parent.right + onClicked: (pageMenu.status === DialogStatus.Closed) ? pageMenu.open() : pageMenu.close() + } } } --- qml/ImageSlideshowPage.qml +++ qml/ImageSlideshowPage.qml @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2012 Antti Seppälä <[email protected]> + * + * You may use this file under the terms of the BSD license as follows: + * + * "Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Nemo Mobile nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + */ + +import QtQuick 1.1 +import com.nokia.meego 1.0 +import QtMobility.gallery 1.1 + +Page { + id: imageSlideshow + anchors.fill: parent + + property int slideVisibleTime: 4000 + property int visibleIndex + property int phase + property variant galleryModel + + Image { + id: image1 + asynchronous: true + width: Math.min(sourceSize.width, parent.width) + height: Math.min(sourceSize.height, parent.height) + anchors.centerIn: parent + opacity: 1 + fillMode: Image.PreserveAspectFit + Behavior on opacity { NumberAnimation { duration: 1000 } } + } + Image { + id: image2 + asynchronous: true + width: Math.min(sourceSize.width, parent.width) + height: Math.min(sourceSize.height, parent.height) + anchors.centerIn: parent + opacity: 0 + fillMode: Image.PreserveAspectFit + Behavior on opacity { NumberAnimation { duration: 1000 } } + } + SequentialAnimation { + id: mainLoop + loops: Animation.Infinite + PauseAnimation { duration: imageSlideshow.slideVisibleTime } + ScriptAction { + script: { + image1.opacity = image2.opacity + image2.opacity = 1-image2.opacity + } + } + PauseAnimation { duration: 1001 } + ScriptAction { script: loadNextImage() } + } + MouseArea { + anchors.fill: parent + onPressed: { + mainLoop.stop() + image1.source = "" + image2.source = "" + appWindow.fullscreen = false + appWindow.pageStack.pop() + } + } + Component.onCompleted: { + appWindow.fullscreen = true + if (visibleIndex < galleryModel.count) { + phase = 0 + image1.source = galleryModel.get(visibleIndex).url + loadNextImage() + mainLoop.start() + } + } + function loadNextImage() { + visibleIndex++ + if (visibleIndex >= galleryModel.count) + visibleIndex = 0 + + if (phase === 0) + image2.source = galleryModel.get(visibleIndex).url + else + image1.source = galleryModel.get(visibleIndex).url + phase = 1-phase + } +} --- qml/MainPage.qml +++ qml/MainPage.qml @@ -45,7 +45,7 @@ delegate: GalleryDelegate { MouseArea { anchors.fill: parent - onClicked: appWindow.pageStack.push(Qt.resolvedUrl("ImagePage.qml"), {imageId: index, galleryModel: gallery } ) + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("ImagePage.qml"), {visibleIndex: index, galleryModel: gallery} ) } } } @@ -55,13 +55,23 @@ ToolIcon { platformIconId: "toolbar-view-menu" anchors.right: (parent === undefined) ? undefined : parent.right - //onClicked: (myMenu.status == DialogStatus.Closed) ? myMenu.open() : myMenu.close() + onClicked: (pageMenu.status === DialogStatus.Closed) ? pageMenu.open() : pageMenu.close() + } + } + + Menu { + id: pageMenu + MenuLayout { + MenuItem { + text: "Slideshow" + onClicked: appWindow.pageStack.push(Qt.resolvedUrl("ImageSlideshowPage.qml"), {visibleIndex: 0, galleryModel: gallery}) + } } } states: State { name: "active" - when: status == PageStatus.Active || status == PageStatus.Activating + when: status === PageStatus.Active || status === PageStatus.Activating PropertyChanges { target: appWindow.pageStack.toolBar --- qml/api/GalleryModel.qml +++ qml/api/GalleryModel.qml @@ -34,10 +34,7 @@ DocumentGalleryModel { id: gallery - rootType : DocumentGallery.Image - properties : [ "url", "width", "height" ] - filter : GalleryWildcardFilter { - property : "fileName"; - value : "*.jpg"; - } + autoUpdate: true + rootType: DocumentGallery.Image + properties: [ "url", "width", "height" ] } --- qml/api/GalleryView.qml +++ qml/api/GalleryView.qml @@ -30,18 +30,18 @@ */ import QtQuick 1.1 +import com.nokia.meego 1.0 import org.nemomobile.thumbnailer 1.0 GridView { + id: grid + anchors.fill: parent + // baseThumbnailSize is used to request images, and display size will be <= property int baseThumbnailSize: 160 property int thumbnailSize: 0 property int padding: 2 - Component.onCompleted: updateThumbnailSize() - onPaddingChanged: updateThumbnailSize() - onBaseThumbnailSizeChanged: updateThumbnailSize() - // Calculate the thumbnail size to fit items of approximately 160px // onto each row with a minimal amount of extra space. The goal is // to avoid having a large unused area on the right edge. @@ -53,17 +53,40 @@ console.log("XXX: Thumb size is " + thumbnailSize) } - Connections { - target: screen - onCurrentOrientationChanged: updateThumbnailSize() - } - - id: grid - anchors.fill: parent + Component.onCompleted: updateThumbnailSize() + onPaddingChanged: updateThumbnailSize() + onBaseThumbnailSizeChanged: updateThumbnailSize() flow: GridView.LeftToRight maximumFlickVelocity: 3000 cellHeight: thumbnailSize + padding cellWidth: thumbnailSize + padding cacheBuffer: cellHeight * 3 + + ScrollDecorator { + flickableItem: grid + anchors.right: grid.right; anchors.bottom: grid.bottom + } + + Connections { + target: screen + onCurrentOrientationChanged: updateThumbnailSize() + } + + Text { + id: noElementsFoundText + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 20 + anchors.rightMargin: 20 + + visible: parent.model.count == 0 && parent.model.progress == 1.0 + + text: parent.model.filter == null ? "No images yet..." : "No matching images found..." + color: "lightgrey" + font.pointSize: 26 + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + } } --- qml/main.qml +++ qml/main.qml @@ -36,6 +36,9 @@ id: appWindow initialPage: mainPage + property bool fullscreen: false + showStatusBar: !fullscreen + showToolBar: !fullscreen MainPage { id: mainPage --- qmlgallery.desktop +++ qmlgallery.desktop @@ -3,7 +3,7 @@ Version=1.0 Type=Application Terminal=false -Name=qmlgallery +Name=Gallery Exec=/opt/qmlgallery/bin/qmlgallery Icon=icons-Applications-photos X-Window-Icon= --- res.qrc +++ res.qrc @@ -1,7 +1,9 @@ <RCC> <qresource> + <file>qml/ImageContainer.qml</file> <file>qml/ImagePage.qml</file> <file>qml/MainPage.qml</file> <file>qml/main.qml</file> + <file>qml/ImageSlideshowPage.qml</file> </qresource> </RCC> ++++++ qmlgallery.yaml --- qmlgallery.yaml +++ qmlgallery.yaml @@ -1,6 +1,6 @@ Name: qmlgallery Summary: Photo Gallery for Nemo -Version: 0.0.7 +Version: 0.0.8 Release: 1 Group: Applications/System License: BSD
