- Enginio Manual
- Enginio QML Types and Examples
- Enginio QML Examples - Image Gallery
- image-gallery.qml Example File
- Enginio 1.2.1 Reference Documentation

# image-gallery.qml Example File

image-gallery/image-gallery.qml

    /****************************************************************************
    **
    ** Copyright (C) 2015 The Qt Company Ltd.
    ** Contact: http://www.qt.io/licensing/
    **
    ** This file is part of the examples of the Qt Toolkit.
    **
    ** $QT_BEGIN_LICENSE:BSD$
    ** 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 The Qt Company Ltd 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."
    **
    ** $QT_END_LICENSE$
    **
    ****************************************************************************/

    import QtQuick 2.0
    import Enginio 1.0

    import QtQuick.Dialogs 1.0
    import QtQuick.Controls 1.0
    import QtQuick.Layouts 1.0

    /*
     * Enginio image gallery example.
     *
     * Main window contains list of enginioModel on the backend and button for uploading
     * new enginioModel. Image list contains image thumbnail (generated by Enginio
     * backend) and some image metadata. Clicking list items downloads image file
     * and displays it in dialog window. Clicking the red x deletes enginioModel from
     * backend.
     *
     * In the backend enginioModel are represented as objects of type "objects.image". These
     * objects contain a property "file" which is a reference to the actual binary file.
     */

    Item {
        id: main
        width: 460
        height: 640

        BackendHelper{
            id: backendHelper
        }

        property var imagesUrl: new Object
        Rectangle {
            id: root
            anchors.fill: parent
            opacity: 1 - backendHelper.opacity
            color: "#f4f4f4"

            // Enginio client specifies the backend to be used
            EnginioClient {
                id: client
                backendId: backendHelper.backendId
                onError: console.log("Enginio error: " + reply.errorCode + ": " + reply.errorString)
            }

            EnginioModel {
                id: enginioModel
                client: client
                query: { // query for all objects of type "objects.image" and include not null references to files
                    "objectType": "objects.image",
                    "include": {"file": {}},
                    "query" : { "file": { "$ne": null } }
                }
            }

            // Delegate for displaying individual rows of the model
            Component {
                id: imageListDelegate

                BorderImage {
                    height: 120
                    width: parent.width
                    border.top: 4
                    border.bottom: 4
                    source: hitbox.pressed ? "qrc:images/delegate_pressed.png" : "qrc:images/delegate.png"

                    Image {
                        id: shadow
                        anchors.top: parent.bottom
                        width: parent.width
                        visible: !hitbox.pressed
                        source: "qrc:images/shadow.png"
                    }

                    Image {
                        id: image
                        x: 10
                        width: 100
                        height: 100
                        anchors.verticalCenter: parent.verticalCenter
                        opacity: image.status == Image.Ready ? 1 : 0
                        Behavior on opacity { NumberAnimation { duration: 100 } }
                        Component.onCompleted: {
                            if (id in imagesUrl) {
                                image.source = imagesUrl[id]
                            } else {
                                var data = { "id": file.id,
                                    "variant": "thumbnail"}
                                var reply = client.downloadUrl(data)
                                reply.finished.connect(function() {
                                    imagesUrl[id] = reply.data.expiringUrl
                                    if (image && reply.data.expiringUrl) // It may be deleted as it is delegate
                                        image.source = reply.data.expiringUrl
                                })
                            }
                        }
                    }
                    Rectangle {
                        color: "transparent"
                        anchors.fill: image
                        border.color: "#aaa"
                        Rectangle {
                            id: progressBar
                            property real value:  image.progress
                            anchors.bottom: parent.bottom
                            width: image.width * value
                            height: 4
                            color: "#49f"
                            opacity: image.status != Image.Ready ? 1 : 0
                            Behavior on opacity {NumberAnimation {duration: 100}}
                        }
                    }

                    Column {
                        anchors.left: image.right
                        anchors.right: deleteIcon.left
                        anchors.margins: 12
                        y: 10
                        Text {
                            height: 33
                            width: parent.width
                            verticalAlignment: Text.AlignVCenter
                            font.pixelSize: height * 0.5
                            text: name ? name : ""
                            elide: Text.ElideRight
                        }
                        Text {
                            height: 33
                            width: parent.width
                            verticalAlignment: Text.AlignVCenter
                            font.pixelSize: height * 0.5
                            text: sizeStringFromFile(file)
                            elide:Text.ElideRight
                            color: "#555"
                        }
                        Text {
                            height: 33
                            width: parent.width
                            verticalAlignment: Text.AlignVCenter
                            font.pixelSize: height * 0.5
                            text: timeStringFromFile(file)
                            elide:Text.ElideRight
                            color: "#555"
                        }
                    }

                    // Clicking list item opens full size image in separate dialog
                    MouseArea {
                        id: hitbox
                        anchors.fill: parent
                        onClicked: {
                            imageDialog.fileId = file.id;
                            imageDialog.visible = true
                            root.state = "view"
                        }
                    }

                    // Delete button
                    Image {
                        id: deleteIcon
                        anchors.right: parent.right
                        anchors.verticalCenter: parent.verticalCenter
                        anchors.rightMargin: 18
                        source: removeMouseArea.pressed ?"qrc:icons/delete_icon_pressed.png" : "qrc:icons/delete_icon.png"
                        MouseArea {
                            id: removeMouseArea
                            anchors.fill: parent
                            anchors.margins: -10
                            onClicked: enginioModel.remove(index)
                        }
                    }
                }
            }

            // A simple layout:
            // a listview and a line edit with button to add to the list
            Rectangle {
                id: header
                anchors.top: parent.top
                width: parent.width
                height: 70
                color: "white"

                Row {
                    id: logo
                    anchors.centerIn: parent
                    anchors.horizontalCenterOffset: -4
                    spacing: 6
                    Image {
                        source: "qrc:images/enginio.png"
                        width:160 ; height: 60
                        fillMode:Image.PreserveAspectFit
                    }
                    Text {
                        text: "Gallery"
                        anchors.verticalCenter: parent.verticalCenter
                        anchors.verticalCenterOffset: -3
                        font.bold: true
                        font.pixelSize: 46
                        color: "#555"
                    }
                }
                Rectangle {
                    width: parent.width ; height: 1
                    anchors.bottom: parent.bottom
                    color: "#bbb"
                }
            }

            Row {
                id: listLayout

                Behavior on x {NumberAnimation{ duration: 400 ; easing.type: "InOutCubic"}}
                anchors.top: header.bottom
                anchors.bottom: footer.top

                ListView {
                    id: imageListView
                    model: enginioModel // get the data from EnginioModel
                    delegate: imageListDelegate
                    clip: true
                    width: root.width
                    height: parent.height
                    // Animations
                    add: Transition { NumberAnimation { properties: "y"; from: root.height; duration: 250 } }
                    removeDisplaced: Transition { NumberAnimation { properties: "y"; duration: 150 } }
                    remove: Transition { NumberAnimation { property: "opacity"; to: 0; duration: 150 } }
                }

                // Dialog for showing full size image
                Rectangle {
                    id: imageDialog
                    width: root.width
                    height: parent.height
                    property string fileId
                    color: "#333"

                    onFileIdChanged: {
                        image.source = ""
                        // Download the full image, not the thumbnail
                        var data = { "id": fileId }
                        var reply = client.downloadUrl(data)
                        reply.finished.connect(function() {
                            image.source = reply.data.expiringUrl
                        })
                    }
                    Label {
                        id: label
                        text: "Loading ..."
                        font.pixelSize: 28
                        color: "white"
                        anchors.centerIn: parent
                        visible: image.status != Image.Ready
                    }
                    Rectangle {
                        property real value: image.progress
                        anchors.bottom: parent.bottom
                        width: parent.width * value
                        height: 4
                        color: "#49f"
                        Behavior on opacity {NumberAnimation {duration: 200}}
                        opacity: image.status !== Image.Ready ? 1 : 0
                    }
                    Image {
                        id: image
                        anchors.fill: parent
                        anchors.margins: 10
                        smooth: true
                        cache: false
                        fillMode: Image.PreserveAspectFit
                        Behavior on opacity { NumberAnimation { duration: 100 } }
                        opacity: image.status === Image.Ready ? 1 : 0
                    }
                    MouseArea {
                        anchors.fill: parent
                        onClicked: root.state = ""
                    }
                }
            }

            BorderImage {
                id: footer

                width: parent.width
                anchors.bottom: parent.bottom
                source: addMouseArea.pressed ? "qrc:images/delegate_pressed.png" : "qrc:images/delegate.png"
                border.left: 5; border.top: 5
                border.right: 5; border.bottom: 5

                Rectangle {
                    y: -1 ; height: 1
                    width: parent.width
                    color: "#bbb"
                }
                Rectangle {
                    y: 0 ; height: 1
                    width: parent.width
                    color: addMouseArea.pressed ? "transparent" : "white"
                }

                Text {
                    text: "Click to upload..."
                    font.bold: true
                    font.pixelSize: 28
                    color: "#444"
                    anchors.centerIn: parent
                }

                Item {
                    id: addButton
                    width: 40 ; height: 40
                    anchors.margins: 20
                    anchors.right: parent.right
                    anchors.verticalCenter: parent.verticalCenter
                    Image {
                        id: removeIcon
                        source: addMouseArea.pressed ? "qrc:icons/add_icon_pressed.png" : "qrc:icons/add_icon.png"
                        anchors.centerIn: parent
                    }
                }

                MouseArea {
                    id: addMouseArea
                    anchors.fill: parent
                    onClicked: fileDialog.visible = true;
                }
                Rectangle {
                    id: progressBar
                    property real value:0
                    anchors.bottom: parent.bottom
                    width: parent.width * value
                    height: 4
                    color: "#49f"
                    Behavior on opacity {NumberAnimation {duration: 100}}
                }
            }

            // File dialog for selecting image file from local file system
            FileDialog {
                id: fileDialog
                title: "Select image file to upload"
                nameFilters: [ "Image files (*.png *.jpg *.jpeg)", "All files (*)" ]

                onSelectionAccepted: {
                    var pathParts = fileUrl.toString().split("/");
                    var fileName = pathParts[pathParts.length - 1];
                    var fileObject = {
                        objectType: "objects.image",
                        name: fileName,
                        localPath: fileUrl.toString()
                    }
                    var reply = client.create(fileObject);
                    reply.finished.connect(function() {
                        var uploadData = {
                            file: { fileName: fileName },
                            targetFileProperty: {
                                objectType: "objects.image",
                                id: reply.data.id,
                                propertyName: "file"
                            },
                        };

                        imagesUrl[reply.data.id] = reply.data.localPath

                        var uploadReply = client.uploadFile(uploadData, fileUrl)
                        progressBar.opacity = 1
                        uploadReply.progress.connect(function(progress, total) {
                            progressBar.value = progress/total
                        })
                        uploadReply.finished.connect(function() {
                            var tmp = enginioModel.query; enginioModel.query = null; enginioModel.query = tmp;
                            progressBar.opacity = 0
                        })
                    })
                }
            }

            states: [
                State {
                    name: "view"
                    PropertyChanges {
                        target: listLayout
                        x: -root.width
                    }
                }
            ]
        }
        function sizeStringFromFile(fileData) {
            var str = [];
            if (fileData && fileData.fileSize) {
                str.push("Size: ");
                str.push(fileData.fileSize);
                str.push(" bytes");
            }
            return str.join("");
        }

        function doubleDigitNumber(number) {
            if (number < 10)
                return "0" + number;
            return number;
        }

        function timeStringFromFile(fileData) {
            var str = [];
            if (fileData && fileData.createdAt) {
                var date = new Date(fileData.createdAt);
                if (date) {
                    str.push("Uploaded: ");
                    str.push(date.toDateString());
                    str.push(" ");
                    str.push(doubleDigitNumber(date.getHours()));
                    str.push(":");
                    str.push(doubleDigitNumber(date.getMinutes()));
                }
            }
            return str.join("");
        }
    }