← Back to team overview

ubuntu-touch-coreapps-reviewers team mailing list archive

[Merge] lp:~verzegnassi-stefano/ubuntu-docviewer-app/reboot-lok-qsg-zoom into lp:ubuntu-docviewer-app/reboot

 

Stefano Verzegnassi has proposed merging lp:~verzegnassi-stefano/ubuntu-docviewer-app/reboot-lok-qsg-zoom into lp:ubuntu-docviewer-app/reboot.

Commit message:
[loviewer] Implemented zoom:
* Added a manual zoom mode
* Added an automatic zoom mode (fit zoom to flickable width)
* Added a bottom panel with a zoom selector (which includes a TextField and an OptionSelector)

Requested reviews:
  Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot): continuous-integration
  Ubuntu Document Viewer Developers (ubuntu-docviewer-dev)

For more details, see:
https://code.launchpad.net/~verzegnassi-stefano/ubuntu-docviewer-app/reboot-lok-qsg-zoom/+merge/271895

[loviewer] Implemented zoom:
* Added a manual zoom mode
* Added an automatic zoom mode (fit zoom to flickable width)
* Added a bottom panel with a zoom selector (which includes a TextField and an OptionSelector)
-- 
Your team Ubuntu Document Viewer Developers is requested to review the proposed merge of lp:~verzegnassi-stefano/ubuntu-docviewer-app/reboot-lok-qsg-zoom into lp:ubuntu-docviewer-app/reboot.
=== modified file 'po/com.ubuntu.docviewer.pot'
--- po/com.ubuntu.docviewer.pot	2015-09-21 10:39:04 +0000
+++ po/com.ubuntu.docviewer.pot	2015-09-22 19:03:33 +0000
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-09-21 12:38+0200\n"
+"POT-Creation-Date: 2015-09-22 21:00+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@xxxxxx>\n"
@@ -19,12 +19,12 @@
 "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
 
 #: ../src/app/docviewer-application.cpp:162
-#: /tmp/build-20-reboot-contenthub-update-translations-Desktop-Default/po/com.ubuntu.docviewer.desktop.in.in.h:1
+#: /home/stefano/build-reboot-qsg-impress-support-Desktop-Default/po/com.ubuntu.docviewer.desktop.in.in.h:1
 msgid "Document Viewer"
 msgstr ""
 
 #: ../src/app/qml/common/DetailsPage.qml:27
-#: ../src/app/qml/loView/LOViewDefaultHeader.qml:112
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:114
 #: ../src/app/qml/pdfView/PdfViewDefaultHeader.qml:97
 #: ../src/app/qml/textView/TextViewDefaultHeader.qml:83
 msgid "Details"
@@ -63,7 +63,7 @@
 #: ../src/app/qml/common/RejectedImportDialog.qml:38
 #: ../src/app/qml/documentPage/DocumentPageSelectionModeHeader.qml:32
 #: ../src/app/qml/documentPage/SortSettingsDialog.qml:53
-#: ../src/app/qml/loView/LOViewDefaultHeader.qml:76
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:79
 #: ../src/app/qml/pdfView/PdfViewDefaultHeader.qml:61
 #: ../src/app/qml/textView/TextViewDefaultHeader.qml:61
 msgid "Close"
@@ -151,7 +151,6 @@
 
 #: ../src/app/qml/documentPage/DeleteFileDialog.qml:44
 #: ../src/app/qml/documentPage/DocumentPagePickModeHeader.qml:28
-#: ../src/app/qml/loView/LOViewGotoDialog.qml:52
 #: ../src/app/qml/pdfView/PdfViewGotoDialog.qml:52
 msgid "Cancel"
 msgstr ""
@@ -254,7 +253,8 @@
 msgstr ""
 
 #: ../src/app/qml/documentPage/DocumentPageSearchHeader.qml:27
-#: ../src/app/qml/loView/LOViewDefaultHeader.qml:76
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:79
+#: ../src/app/qml/loView/LOViewPage.qml:186
 #: ../src/app/qml/pdfView/PdfViewDefaultHeader.qml:61
 #: ../src/app/qml/textView/TextViewDefaultHeader.qml:61
 msgid "Back"
@@ -305,61 +305,85 @@
 msgid "Reverse order"
 msgstr ""
 
-#: ../src/app/qml/loView/LOViewDefaultHeader.qml:57
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:56
+#: ../src/app/qml/textView/TextView.qml:42
+msgid "Loading..."
+msgstr ""
+
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:60
 msgid "LibreOffice text document"
 msgstr ""
 
-#: ../src/app/qml/loView/LOViewDefaultHeader.qml:59
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:62
 msgid "LibreOffice spread sheet"
 msgstr ""
 
-#: ../src/app/qml/loView/LOViewDefaultHeader.qml:61
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:64
 msgid "LibreOffice presentation"
 msgstr ""
 
-#: ../src/app/qml/loView/LOViewDefaultHeader.qml:63
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:66
 msgid "LibreOffice Draw document"
 msgstr ""
 
-#: ../src/app/qml/loView/LOViewDefaultHeader.qml:65
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:68
 msgid "Unknown LibreOffice document"
 msgstr ""
 
-#: ../src/app/qml/loView/LOViewDefaultHeader.qml:67
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:70
 msgid "Unknown type document"
 msgstr ""
 
-#: ../src/app/qml/loView/LOViewDefaultHeader.qml:100
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:95
+msgid "Show zoom controls"
+msgstr ""
+
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:102
 #: ../src/app/qml/pdfView/PdfViewDefaultHeader.qml:85
 msgid "Go to page..."
 msgstr ""
 
-#: ../src/app/qml/loView/LOViewDefaultHeader.qml:106
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:108
 #: ../src/app/qml/pdfView/PdfViewDefaultHeader.qml:91
 #: ../src/app/qml/textView/TextViewDefaultHeader.qml:77
 msgid "Disable night mode"
 msgstr ""
 
-#: ../src/app/qml/loView/LOViewDefaultHeader.qml:106
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:108
 #: ../src/app/qml/pdfView/PdfViewDefaultHeader.qml:91
 #: ../src/app/qml/textView/TextViewDefaultHeader.qml:77
 msgid "Enable night mode"
 msgstr ""
 
-#: ../src/app/qml/loView/LOViewGotoDialog.qml:25
-#: ../src/app/qml/pdfView/PdfViewGotoDialog.qml:25
-msgid "Go to page"
-msgstr ""
-
-#: ../src/app/qml/loView/LOViewGotoDialog.qml:26
-#: ../src/app/qml/pdfView/PdfViewGotoDialog.qml:26
-#, qt-format
-msgid "Choose a page between 1 and %1"
-msgstr ""
-
-#: ../src/app/qml/loView/LOViewGotoDialog.qml:44
-#: ../src/app/qml/pdfView/PdfViewGotoDialog.qml:44
-msgid "GO!"
+#: ../src/app/qml/loView/LOViewPage.qml:41
+#: ../src/app/qml/loView/LOViewPage.qml:184
+msgid "Slides"
+msgstr ""
+
+#: ../src/app/qml/loView/LOViewZoomHeader.qml:42
+msgid "Hide zoom controls"
+msgstr ""
+
+#: ../src/app/qml/loView/LOViewZoomHeader.qml:49
+msgid "Zoom in"
+msgstr ""
+
+#: ../src/app/qml/loView/LOViewZoomHeader.qml:55
+msgid "Zoom out"
+msgstr ""
+
+#: ../src/app/qml/loView/ZoomSelector.qml:29
+msgid "Automatic (Fit width)"
+msgstr ""
+
+#: ../src/app/qml/loView/ZoomSelector.qml:94
+#, qt-format
+msgid "Automatic (%1%)"
+msgstr ""
+
+#: ../src/app/qml/loView/ZoomSelector.qml:95
+#, qt-format
+msgid "Zoom: %1%"
 msgstr ""
 
 #. TRANSLATORS: "Contents" refers to the "Table of Contents" of a PDF document.
@@ -379,8 +403,17 @@
 msgid "Page %1 of %2"
 msgstr ""
 
-#: ../src/app/qml/textView/TextView.qml:42
-msgid "Loading..."
+#: ../src/app/qml/pdfView/PdfViewGotoDialog.qml:25
+msgid "Go to page"
+msgstr ""
+
+#: ../src/app/qml/pdfView/PdfViewGotoDialog.qml:26
+#, qt-format
+msgid "Choose a page between 1 and %1"
+msgstr ""
+
+#: ../src/app/qml/pdfView/PdfViewGotoDialog.qml:44
+msgid "GO!"
 msgstr ""
 
 #. TRANSLATORS: This string is used for renaming a copied file,
@@ -392,11 +425,11 @@
 #.
 #. where "2" is given by the argument "%1"
 #.
-#: ../src/plugin/file-qml-plugin/docviewerutils.cpp:99
+#: ../src/plugin/file-qml-plugin/docviewerutils.cpp:101
 #, qt-format
 msgid "copy %1"
 msgstr ""
 
-#: /tmp/build-20-reboot-contenthub-update-translations-Desktop-Default/po/com.ubuntu.docviewer.desktop.in.in.h:2
+#: /home/stefano/build-reboot-qsg-impress-support-Desktop-Default/po/com.ubuntu.docviewer.desktop.in.in.h:2
 msgid "documents;viewer;pdf;reader;"
 msgstr ""

=== modified file 'src/app/main.cpp'
--- src/app/main.cpp	2015-03-03 16:49:48 +0000
+++ src/app/main.cpp	2015-09-22 19:03:33 +0000
@@ -19,6 +19,7 @@
 
 // Uncomment if you need to use QML analyzer
 // #define QT_QML_DEBUG
+// #include <QtQuick>
 
 #include "docviewer-application.h"
 #include <QDebug>

=== added file 'src/app/qml/loView/KeybHelper.js'
--- src/app/qml/loView/KeybHelper.js	1970-01-01 00:00:00 +0000
+++ src/app/qml/loView/KeybHelper.js	2015-09-22 19:03:33 +0000
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 Stefano Verzegnassi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+ 
+function parseEvent(event) {
+    var pixelDiff = 5;
+ 
+    if (event.key == Qt.Key_PageUp) {
+        if (loDocument.documentType == LO.Document.PresentationDocument)
+            loDocument.currentPart -= 1
+        else
+            loPage.moveView("vertical", -loView.height)
+ 
+        return;
+    }
+ 
+    if (event.key == Qt.Key_PageDown) {
+        if (loDocument.documentType == LO.Document.PresentationDocument)
+            loDocument.currentPart += 1
+        else
+            loPage.moveView("vertical", loView.height)
+ 
+        return;
+    }
+ 
+    if (event.key == Qt.Key_Up) {
+        loPage.moveView("vertical", -pixelDiff)
+        return;
+    }
+ 
+    if (event.key == Qt.Key_Down) {
+        loPage.moveView("vertical", pixelDiff)
+        return;
+    }
+ 
+    if (event.key == Qt.Key_Left) {
+        loPage.moveView("horizontal", -pixelDiff)
+        return;
+    }
+ 
+    if (event.key == Qt.Key_Right) {
+        loPage.moveView("horizontal", pixelDiff)
+        return;
+    }
+}

=== modified file 'src/app/qml/loView/LOViewDefaultHeader.qml'
--- src/app/qml/loView/LOViewDefaultHeader.qml	2015-06-26 14:56:23 +0000
+++ src/app/qml/loView/LOViewDefaultHeader.qml	2015-09-22 19:03:33 +0000
@@ -52,7 +52,10 @@
 
                 fontSize: "small"
                 text: {
-                    switch(loDocument.documentType) {
+                    if (!loPageContentLoader.item)
+                        return i18n.tr("Loading...")
+
+                    switch(loPageContentLoader.item.loDocument.documentType) {
                     case 0:
                         return i18n.tr("LibreOffice text document")
                     case 1:
@@ -88,17 +91,16 @@
 
     actions: [
         Action {
-            iconName: "search"
-            // onTriggered: pageMain.state = "search"
-            //Disable it until we provide search in Poppler plugin.
-            enabled: false
+            iconName: "zoom-in"
+            text: i18n.tr("Show zoom controls")
+            onTriggered: targetPage.state = "zoom"
         },
 
         Action {
             objectName:"gotopage"
             iconName: "browser-tabs"
             text: i18n.tr("Go to page...")
-            onTriggered: PopupUtils.open(Qt.resolvedUrl("LOViewGotoDialog.qml"), targetPage)
+            enabled: false
         },
 
         Action {

=== removed file 'src/app/qml/loView/LOViewDelegate.qml'
--- src/app/qml/loView/LOViewDelegate.qml	2015-06-24 12:04:16 +0000
+++ src/app/qml/loView/LOViewDelegate.qml	1970-01-01 00:00:00 +0000
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2013-2015 Canonical, Ltd.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-import QtQuick 2.3
-import Ubuntu.Components 1.1
-
-Rectangle {
-    id: loPage
-
-    property int index: model.index
-    property bool _previewFetched: false
-
-    property alias status: pageImg.status
-
-    width: parent.width
-    height: width * (model.height / model.width)
-    color: "#E6E6E6"
-
-    // Preview page rendering. Used as placeholder while zooming the page.
-    // We generate the low resolution preview from the texture of the PDF page,
-    // so that we can keep page rendering as fast as possible.
-    ShaderEffectSource {
-        id: previewImg
-        anchors.fill: parent
-
-        // We cannot change its opacity or visibility, otherwise the texture will be refreshed,
-        // even if live is false.
-        live: false
-        textureSize: Qt.size(256, 256 * (model.height / model.width))
-    }
-
-    Image {
-        id: pageImg
-        anchors.fill: parent
-
-        source: "image://libreoffice/page/" + index;
-        sourceSize.width: loPage.width
-
-        onStatusChanged: {
-            // This is supposed to run the first time PdfViewDelegate gets the page rendering.
-            if (!_previewFetched) {
-                if (status == Image.Ready) {
-                    previewImg.sourceItem = pageImg
-                    // Re-assign sourceItem property, so the texture is not updated when Image status changes.
-                    previewImg.sourceItem = loPage
-                }
-            }
-        }
-
-        // Request a new page rendering. The order, which pages are requested with, depends on the distance from the currentPage
-        Timer {
-            id: _zoomTimer
-            interval: {
-                var diff = Math.abs(loView.currentPageIndex - model.index)
-                var prov = loDocument.providersNumber * 0.5
-
-                if (diff < prov)
-                    return 0
-                else
-                    return (diff - prov) * 10
-            }
-
-            onTriggered: {
-                pageImg.sourceSize.width = loPage.width;
-            }
-        }
-    }
-
-    // Page rendering depends on the width of PdfViewDelegate.
-    // Because of this, we have multiple callings to ImageProvider while zooming.
-    // Just avoid it.
-    Connections {
-        target: pinchy
-
-        onPinchStarted: _zoomTimer.stop();
-        onPinchUpdated: {
-            // This ensures that page image is not reloaded when the maximumScale or minimumScale has already been reached.
-            if ( !(_zoomHelper.scale >= 2.5 && pinch.scale > 1.0) && !(_zoomHelper.scale <= 1.0 && pinch.scale < 1.0) )
-                pageImg.sourceSize.width = 0;
-        }
-        onPinchFinished: _zoomTimer.restart();
-    }
-}

=== removed file 'src/app/qml/loView/LOViewGotoDialog.qml'
--- src/app/qml/loView/LOViewGotoDialog.qml	2015-06-24 12:04:16 +0000
+++ src/app/qml/loView/LOViewGotoDialog.qml	1970-01-01 00:00:00 +0000
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2014-2015 Canonical, Ltd.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-import QtQuick 2.3
-import Ubuntu.Components 1.1
-import Ubuntu.Components.Popups 1.0
-
-Dialog {
-    id: goToPageDialog
-    objectName:"LOViewGotoDialog"
-
-    title: i18n.tr("Go to page")
-    text: i18n.tr("Choose a page between 1 and %1").arg(loView.count)
-
-    TextField {
-        id: goToPageTextField
-        objectName:"goToPageTextField"
-
-        width: parent.width
-
-        hasClearButton: true
-        inputMethodHints: Qt.ImhFormattedNumbersOnly
-        validator: IntValidator{ bottom: 1; top: loView.count }
-
-        Keys.onReturnPressed: goToPage()
-        Component.onCompleted: forceActiveFocus()
-    }
-
-    Button {
-        objectName:"GOButton"
-        text: i18n.tr("GO!")
-        color: UbuntuColors.green
-
-        enabled: goToPageTextField.acceptableInput
-        onClicked: goToPage()
-    }
-
-    Button {
-        text: i18n.tr("Cancel")
-        onClicked: PopupUtils.close(goToPageDialog)
-    }
-
-    function goToPage() {
-        loView.positionAtIndex((goToPageTextField.text - 1))
-        PopupUtils.close(goToPageDialog)
-    }
-}

=== modified file 'src/app/qml/loView/LOViewPage.qml'
--- src/app/qml/loView/LOViewPage.qml	2015-09-19 15:16:51 +0000
+++ src/app/qml/loView/LOViewPage.qml	2015-09-22 19:03:33 +0000
@@ -16,28 +16,53 @@
 
 import QtQuick 2.3
 import Ubuntu.Components 1.1
+import Ubuntu.Layouts 1.0
 import DocumentViewer.LibreOffice 1.0 as LO
 
+import "../upstreamComponents"
+
 import "../common/utils.js" as Utils
-import "../upstreamComponents"
+import "KeybHelper.js" as KeybHelper
 
-Page {
+PageWithBottomEdge {
     id: loPage
     title: Utils.getNameOfFile(file.path);
-
-    // Disable header auto-hide.
     flickable: null
 
+    readonly property bool wideWindow: width > units.gu(120)
+
+    bottomEdgeEnabled: {
+        if (!loPageContentLoader.loaded)
+            return false
+
+        // else
+        return loPageContentLoader.item.loDocument.documentType == LO.Document.PresentationDocument && !wideWindow
+    }
+    bottomEdgeTitle: i18n.tr("Slides")
+
     Loader {
-        id: contentLoader
+        id: loPageContentLoader
 
         asynchronous: true
         anchors.fill: parent
         sourceComponent: loPageContentComponent
+
+        onLoaded: {
+            if (loaded) {
+                // FIXME: At the moment don't hide header if the document is a presentation
+                var isPresentation = (item.loDocument.documentType === LO.Document.PresentationDocument)
+                loPage.flickable = isPresentation ? null : item.loView
+
+                loPage.bottomEdgePageComponent = item.bottomEdgePartsPage
+
+            } else {
+                loPage.flickable = null
+            }
+        }
     }
 
     ActivityIndicator {
-        running: contentLoader.status != Loader.Ready
+        running: loPageContentLoader.status != Loader.Ready
         visible: running
         anchors.centerIn: parent
     }
@@ -46,32 +71,145 @@
         id: loPageContentComponent
 
         Item {
+            id: loPageContent
+            anchors.fill: parent
             property alias loDocument: loView.document
-
-            LO.Viewer {
-                id: loView
-                objectName: "loView"
+            property alias loView: loView
+            property alias bottomEdgePartsPage: bottomEdgePartsPage
+
+            function moveView(axis, diff) {
+                if (axis == "vertical") {
+                    var maxContentY = Math.max(0, loView.contentHeight - loView.height)
+                    loView.contentY = Math.max(0, Math.min(loView.contentY + diff, maxContentY ))
+                } else {
+                    var maxContentX = Math.max(0, loView.contentWidth - loView.width)
+                    loView.contentX = Math.max(0, Math.min(loView.contentX + diff, maxContentX ))
+                }
+            }
+
+            Layouts {
+                id: layouts
                 anchors.fill: parent
 
-                clip: true
-                documentPath: file.path
-
-                Component.onCompleted: {
-                    // WORKAROUND: Fix for wrong grid unit size
-                    flickDeceleration = 1500 * units.gridUnit / 8
-                    maximumFlickVelocity = 2500 * units.gridUnit / 8
-                }
-            }
-
-            Scrollbar { flickableItem: loView }
-            Scrollbar { flickableItem: loView; align: Qt.AlignBottom }
+                layouts: [
+                    ConditionalLayout {
+                        when: wideWindow
+                        name: "wideWindowLayout"
+
+                        Item {
+                            anchors.fill: parent
+
+                            // TODO: Add a setting to show/hide sidebar when width > units.gu(80)
+                            PartsView {
+                                id: partsView
+                                anchors {
+                                    top: parent.top
+                                    bottom: bottomBarLayoutItem.top
+                                    left: parent.left
+                                }
+
+                                model: partsModel
+                                visible: model
+                                width: visible ? units.gu(40) : 0
+                            }
+
+                            Item {
+                                anchors {
+                                    left: partsView.right
+                                    right: parent.right
+                                    top: parent.top
+                                    bottom: bottomBarLayoutItem.top
+                                }
+                                ItemLayout { item: "loView"; anchors.fill: parent }
+                            }
+
+                            Item {
+                                id: bottomBarLayoutItem
+                                visible: loDocument.documentType == LO.Document.PresentationDocument
+                                height: visible ? units.gu(5) : 0
+                                anchors {
+                                    left: parent.left
+                                    right: parent.right
+                                    bottom: parent.bottom
+                                }
+
+                                ItemLayout { item: "bottomBar"; anchors.fill: parent }
+                            }
+                        }
+                    }
+                ]
+
+                LO.Viewer {
+                    id: loView
+                    objectName: "loView"
+                    Layouts.item: "loView"
+                    anchors {
+                        left: parent.left
+                        right: parent.right
+                        top: parent.top
+                        bottom: bottomBar.top
+                    }
+
+                    clip: true
+                    documentPath: file.path
+
+                    Component.onCompleted: {
+                        // WORKAROUND: Fix for wrong grid unit size
+                        flickDeceleration = 1500 * units.gridUnit / 8
+                        maximumFlickVelocity = 2500 * units.gridUnit / 8
+                    }
+
+                    Scrollbar { flickableItem: loView; parent: loView.parent }
+                    Scrollbar { flickableItem: loView; parent: loView.parent; align: Qt.AlignBottom }
+                }
+
+                // TODO: When we'll have to merge this with the zooming branch, replace this
+                // and use a single bottom panel
+                SlideControllerPanel {
+                    id: bottomBar
+                    Layouts.item: "bottomBar"
+                    visible: loDocument.documentType == LO.Document.PresentationDocument
+                    height: visible ? units.gu(5) : 0
+                    anchors {
+                        left: parent.left
+                        right: parent.right
+                        bottom: parent.bottom
+                    }
+                }
+            }
+
+            Component {
+                id: bottomEdgePartsPage
+                Page {
+                    title: i18n.tr("Slides")
+                    head.backAction: Action {
+                        text: i18n.tr("Back")
+                        iconName: "down"
+                        onTriggered: pageStack.pop()
+                    }
+
+                    flickable: null
+
+                    PartsView {
+                        anchors.fill: parent
+                        model: LO.PartsModel { document: loPageContent.loDocument }
+                    }
+                }
+            }
         }
     }
 
     // *** HEADER ***
     state: "default"
-    states: LOViewDefaultHeader {
-        name: "default"
-        targetPage: loPage
-    }
+    states: [
+        LOViewDefaultHeader {
+            name: "default"
+            targetPage: loPage
+        },
+
+        LOViewZoomHeader {
+            name: "zoom"
+            targetPage: loPage
+        }
+    ]
 }

=== added file 'src/app/qml/loView/LOViewZoomHeader.qml'
--- src/app/qml/loView/LOViewZoomHeader.qml	1970-01-01 00:00:00 +0000
+++ src/app/qml/loView/LOViewZoomHeader.qml	2015-09-22 19:03:33 +0000
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014-2015 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.3
+import Ubuntu.Components 1.1
+import QtQuick.Layouts 1.1
+import Ubuntu.Components.Popups 1.0
+
+PageHeadState {
+    id: rootItem
+
+    property Page targetPage
+    head: targetPage.head
+
+    contents: Item {
+        anchors.fill: parent
+
+        ZoomSelector {
+            width: units.gu(24)
+            anchors {
+                right: parent.right
+                verticalCenter: parent.verticalCenter
+            }
+        }
+    }
+
+    backAction: Action {
+        iconName: "close"
+        text: i18n.tr("Hide zoom controls")
+        onTriggered: targetPage.state = "default"
+    }
+
+    actions: [
+        Action {
+            iconName: "zoom-in"
+            text: i18n.tr("Zoom in")
+            onTriggered: loPageContentLoader.item.loView.zoomFactor += 0.1
+        },
+
+        Action {
+            iconName: "zoom-out"
+            text: i18n.tr("Zoom out")
+            onTriggered: loPageContentLoader.item.loView.zoomFactor -= 0.1
+        }
+    ]
+}

=== added file 'src/app/qml/loView/PanelButton.qml'
--- src/app/qml/loView/PanelButton.qml	1970-01-01 00:00:00 +0000
+++ src/app/qml/loView/PanelButton.qml	2015-09-22 19:03:33 +0000
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+ 
+import QtQuick 2.3
+import Ubuntu.Components 1.1
+ 
+AbstractButton {
+    width: units.gu(4); height: parent.height
+ 
+    property alias iconName: icon.name
+    property alias iconSource: icon.source
+ 
+    Icon {
+        id: icon
+        anchors.centerIn: parent
+        width: units.gu(2.5); height: width
+    }
+}

=== added file 'src/app/qml/loView/PartsView.qml'
--- src/app/qml/loView/PartsView.qml	1970-01-01 00:00:00 +0000
+++ src/app/qml/loView/PartsView.qml	2015-09-22 19:03:33 +0000
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 Stefano Verzegnassi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.3
+import Ubuntu.Components 1.1
+import QtQuick.Layouts 1.1
+import DocumentViewer.LibreOffice 1.0 as LibreOffice
+
+import "../upstreamComponents"
+
+ListView {
+    id: view
+    objectName: "view"
+    clip: true
+
+    property bool expanded: true
+
+    currentIndex: view.model ? view.model.document.currentPart : -1
+
+    delegate: ListItemWithActions {
+        id: delegate
+
+        width: parent.width
+        height: units.gu(16)
+
+        color: (view.model.document.currentPart === model.index) ? Theme.palette.selected.background
+                                                                 : "transparent"
+
+        AbstractButton {
+            objectName: "abstractbutton"
+            anchors.fill: parent
+
+            onClicked: {
+                view.model.document.currentPart = model.index
+                pageStack.pop();
+            }
+        }
+
+        RowLayout {
+            anchors.fill: parent
+            spacing: units.gu(1)
+
+            Image {
+                Layout.fillHeight: true
+                Layout.preferredWidth: height
+                fillMode: Image.PreserveAspectFit
+
+                source: "image://lok/part/" + model.index
+            }
+
+            Label {
+                Layout.fillWidth: true
+                wrapMode: Text.WordWrap
+                text: model.name
+                color: (view.model.document.currentPart === model.index) ? UbuntuColors.orange
+                                                                         : Theme.palette.selected.backgroundText
+            }
+
+            Label {
+                text: model.index + 1
+                color: (view.model.document.currentPart === model.index) ? UbuntuColors.orange
+                                                                         : Theme.palette.selected.backgroundText
+            }
+        }
+    }
+
+    Scrollbar { flickableItem: view; parent: view.parent }
+}

=== added file 'src/app/qml/loView/SlideControllerPanel.qml'
--- src/app/qml/loView/SlideControllerPanel.qml	1970-01-01 00:00:00 +0000
+++ src/app/qml/loView/SlideControllerPanel.qml	2015-09-22 19:03:33 +0000
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 Stefano Verzegnassi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.3
+import Ubuntu.Components 1.1
+import QtQuick.Layouts 1.1
+import Ubuntu.Components.ListItems 1.0 as ListItems
+
+Rectangle {
+    id: bottomBar
+    color: "transparent"
+    height: units.gu(5)
+
+    ListItems.ThinDivider {
+        anchors {
+            top: parent.top
+            left: parent.left
+            right: parent.right
+        }
+    }
+
+    Row {
+        anchors.centerIn: parent
+        spacing: units.gu(2)
+
+        AbstractButton {
+            width: units.gu(4); height: parent.height
+            onClicked: loPageContentLoader.item.loDocument.currentPart -= 1
+
+            Icon {
+                id: icon
+                anchors.centerIn: parent
+                width: units.gu(2.5); height: width
+                name: "go-previous"
+            }
+        }
+
+        Label {
+            text: "%1 of %2".arg(loPageContentLoader.item.loDocument.currentPart + 1)
+                            .arg(loPageContentLoader.item.loDocument.partsCount)
+        }
+
+        AbstractButton {
+            width: units.gu(4); height: parent.height
+            onClicked: loPageContentLoader.item.loDocument.currentPart += 1
+
+            Icon {
+                anchors.centerIn: parent
+                width: units.gu(2.5); height: width
+                name: "go-next"
+            }
+        }
+    }
+}

=== added file 'src/app/qml/loView/ZoomSelector.qml'
--- src/app/qml/loView/ZoomSelector.qml	1970-01-01 00:00:00 +0000
+++ src/app/qml/loView/ZoomSelector.qml	2015-09-22 19:03:33 +0000
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2015 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.3
+import Ubuntu.Components 1.1
+import Ubuntu.Components.Popups 1.0
+import DocumentViewer.LibreOffice 1.0 as LibreOffice
+
+TextField {
+    id: textField
+    anchors.verticalCenter: parent.verticalCenter
+    width: units.gu(24)
+
+    property var values: ["auto", 0.5, 0.7, 0.85, 1.0, 1.25, 1.5, 1.75, 2.0, 3.0, 4.0]
+    property var labels: [
+        i18n.tr("Automatic (Fit width)"),
+        "50%", "70%", "85%", "100%", "125%",
+        "150%", "175%","200%", "300%", "400%"
+    ]
+
+    property bool expanded: false
+
+    hasClearButton: true
+    inputMethodHints: Qt.ImhFormattedNumbersOnly
+    validator: IntValidator{ bottom: 50; top: 400 }
+
+    onAccepted: {
+        loPageContentLoader.item.loView.zoomFactor = parseInt(text) / 100
+        focus = false
+    }
+
+    secondaryItem: AbstractButton {
+        visible: !textField.highlighted
+        id: listButton
+        height: parent.height
+        width: visible ? height : 0
+
+        Rectangle {
+            anchors.left: parent.left
+            anchors.verticalCenter: parent.verticalCenter
+            height: parent.height - units.gu(1)
+
+            width: units.dp(1)
+            color: "Grey"
+            opacity: 0.5
+        }
+
+        Icon {
+            id: _upArrow
+
+            width: units.gu(2)
+            height: width
+            anchors.centerIn: parent
+
+            name: "go-down"
+            color: "Grey"
+            rotation: textField.expanded ? 180 : 0
+
+            Behavior on rotation {
+                UbuntuNumberAnimation {}
+            }
+        }
+
+        onClicked: {
+            textField.expanded = true
+            PopupUtils.open(zoomSelectorDialog, listButton)
+        }
+    }
+
+    onHighlightedChanged: {
+        if (highlighted) {
+            text = parseInt(loPageContentLoader.item.loView.zoomFactor * 100)
+        } else text = ""
+    }
+
+    Label {
+        anchors.verticalCenter: parent.verticalCenter
+        anchors.left: parent.left
+        anchors.leftMargin: units.gu(2)
+        visible: !textField.highlighted
+        text: loPageContentLoader.item.loView.zoomMode == LibreOffice.View.FitToWidth ? i18n.tr("Automatic (%1%)").arg(parseInt(loPageContentLoader.item.loView.zoomFactor*100))
+                                                             : i18n.tr("Zoom: %1%").arg(parseInt(loPageContentLoader.item.loView.zoomFactor*100))
+    }
+
+    Component {
+        id: zoomSelectorDialog
+        Popover {
+            id: zoomSelectorDialogue
+            autoClose: false
+            contentHeight: units.gu(24)
+            contentWidth: units.gu(24)
+            Component.onDestruction: textField.expanded = false
+
+            // We don't use 'autoClose' property, since we want to propagate
+            // mouse/touch events to other items (e.g. when zoomSelectorDialogue
+            // is visible, and user taps the zoom+ button on its right, we want
+            // the zoom button to receive the event).
+            InverseMouseArea {
+                anchors.fill: parent
+                propagateComposedEvents: true
+
+                onPressed: {
+                    mouse.accepted = false
+                    PopupUtils.close(zoomSelectorDialogue)
+                }
+            }
+
+            OptionSelector {
+                expanded: true
+                width: parent.width
+                containerHeight: units.gu(24)
+                model: textField.labels
+                selectedIndex: {
+                    if (loPageContentLoader.item.loView.zoomMode == LibreOffice.View.FitToWidth)
+                        return 0
+
+                    for (var i=0; i<textField.values.length; i++) {
+                        if (values[i] == loView.zoomFactor)
+                            return i
+                    }
+                    return -1
+                }
+
+                onSelectedIndexChanged: {
+                    if (selectedIndex == 0) {
+                        loView.adjustZoomToWidth();
+                        return;
+                    }
+
+                    loPageContentLoader.item.loView.zoomFactor = textField.values[selectedIndex]
+                    PopupUtils.close(zoomSelectorDialogue)
+                }
+            }
+        }
+    }
+}

=== modified file 'src/plugin/libreofficetoolkit-qml-plugin/CMakeLists.txt'
--- src/plugin/libreofficetoolkit-qml-plugin/CMakeLists.txt	2015-09-18 09:35:04 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/CMakeLists.txt	2015-09-22 19:03:33 +0000
@@ -17,6 +17,8 @@
     lodocument.cpp
     loview.cpp
     sgtileitem.cpp
+    lopartsimageprovider.cpp
+    lopartsmodel.cpp
     renderengine.cpp
     ${QML_SRCS}
 )

=== modified file 'src/plugin/libreofficetoolkit-qml-plugin/lodocument.cpp'
--- src/plugin/libreofficetoolkit-qml-plugin/lodocument.cpp	2015-09-18 11:18:17 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/lodocument.cpp	2015-09-22 19:03:33 +0000
@@ -60,6 +60,25 @@
     this->loadDocument(m_path);
 }
 
+int LODocument::currentPart() {
+    if (!m_document)
+        return int(-1);
+ 
+    return m_document->getPart();
+}
+ 
+void LODocument::setCurrentPart(int index)
+{
+    if (!m_document)
+        return;
+ 
+    if (this->currentPart() == index || index < 0 || index > partsCount() - 1)
+        return;
+ 
+    m_document->setPart(index);
+    Q_EMIT currentPartChanged();
+}
+
 // Load the document
 bool LODocument::loadDocument(const QString &pathName)
 {
@@ -106,7 +125,7 @@
 
 // Paint a tile, with size=canvasSize, of the part of the document defined by
 // the rect tileSize.
-QImage LODocument::paintTile(const QSize& canvasSize, const QRect& tileSize)
+QImage LODocument::paintTile(const QSize& canvasSize, const QRect& tileSize, const qreal &zoom)
 {
     QImage result = QImage(canvasSize.width(), canvasSize.height(),  QImage::Format_RGB32);
 
@@ -117,10 +136,10 @@
 
     m_document->paintTile(result.bits(),
                           canvasSize.width(), canvasSize.height(),
-                          Twips::convertPixelsToTwips(tileSize.x()),
-                          Twips::convertPixelsToTwips(tileSize.y()),
-                          Twips::convertPixelsToTwips(tileSize.width()),
-                          Twips::convertPixelsToTwips(tileSize.height()));
+                          Twips::convertPixelsToTwips(tileSize.x(), zoom),
+                          Twips::convertPixelsToTwips(tileSize.y(), zoom),
+                          Twips::convertPixelsToTwips(tileSize.width(), zoom),
+                          Twips::convertPixelsToTwips(tileSize.height(), zoom));
 
 #ifdef DEBUG_TILE_BENCHMARK
     qDebug() << "Time to render the tile:" << renderTimer.elapsed() << "ms";
@@ -129,6 +148,33 @@
     return result.rgbSwapped();
 }
 
+int LODocument::partsCount()
+{
+    if (!m_document)
+        return int(0);
+ 
+    return m_document->getParts();
+}
+ 
+QString LODocument::getPartName(int index) const
+{
+    if (!m_document)
+        return QString();
+ 
+    return QString::fromLatin1(m_document->getPartName(index));
+}
+ 
+// This is used by LOPartsImageProvider to temporarily change the current part,
+// in order to generate thumbnails.
+// FIXME: We need to disable tiled rendering when we're generating the thumbnail.
+int LODocument::swapCurrentPart(int newPartIndex)
+{
+    int oldIndex = this->currentPart();
+ 
+    m_document->setPart(newPartIndex);
+    return oldIndex;
+}
+
 /* Export the file in a given format:
  *  - url is a mandatory argument.
  *  - format is optional. If not specified, lok will try to get it from the file

=== modified file 'src/plugin/libreofficetoolkit-qml-plugin/lodocument.h'
--- src/plugin/libreofficetoolkit-qml-plugin/lodocument.h	2015-09-18 11:18:17 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/lodocument.h	2015-09-22 19:03:33 +0000
@@ -31,6 +31,9 @@
     Q_DISABLE_COPY(LODocument)
 
     Q_PROPERTY(QString      path         READ path         WRITE setPath        NOTIFY pathChanged)
+    Q_PROPERTY(int          currentPart  READ currentPart  WRITE setCurrentPart NOTIFY currentPartChanged)
+    // Declare partsCount as constant at the moment, since LOK-plugin is just a viewer for now.
+    Q_PROPERTY(int          partsCount   READ partsCount                                                    CONSTANT)
     Q_PROPERTY(DocumentType documentType READ documentType                      NOTIFY documentTypeChanged)
     Q_ENUMS(DocumentType)
 
@@ -49,15 +52,27 @@
     QString path() const;
     void setPath(const QString& pathName);
 
+    int currentPart();
+    void setCurrentPart(int index);
+
     DocumentType documentType() const;
 
     QSize documentSize() const;
-    QImage paintTile(const QSize& canvasSize, const QRect& tileSize);
+
+    QImage paintTile(const QSize& canvasSize, const QRect& tileSize, const qreal& zoom = 1.0);
+
+    int partsCount();
+ 
+    QString getPartName(int index) const;
+    int swapCurrentPart(int newPartIndex);
+ 
+    void setPart(int index);
 
     Q_INVOKABLE bool saveAs(QString url, QString format, QString filterOptions);
 
 Q_SIGNALS:
     void pathChanged();
+    void currentPartChanged();
     void documentTypeChanged();
 
 private:

=== added file 'src/plugin/libreofficetoolkit-qml-plugin/lopartsimageprovider.cpp'
--- src/plugin/libreofficetoolkit-qml-plugin/lopartsimageprovider.cpp	1970-01-01 00:00:00 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/lopartsimageprovider.cpp	2015-09-22 19:03:33 +0000
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "lopartsimageprovider.h"
+#include "lodocument.h"
+#include "config.h"
+#include "twips.h"
+
+#include <QDebug>
+
+LOPartsImageProvider::LOPartsImageProvider(LODocument *document)
+    : QQuickImageProvider(QQuickImageProvider::Image, QQuickImageProvider::ForceAsynchronousImageLoading)
+{
+    m_document = document;
+}
+
+QImage LOPartsImageProvider::requestImage(const QString & id, QSize * size, const QSize & requestedSize)
+{
+    Q_UNUSED(size)
+    Q_UNUSED(requestedSize)
+
+    if (m_document->documentType() != LODocument::PresentationDocument)
+        return QImage();
+
+    // Here's the tricky magic. For getting a thumbnail of a document part
+    // (e.g. a specific slide in a Impress document), we need to change the
+    // current active part in LODocument, render the thumbnail, then re-set
+    // the previous value through lok::Document::setPath(index).
+    QString type = id.section("/", 0, 0);
+
+    if (type != "part")
+        return QImage();
+
+    int partNumber = id.section("/", 1, 1).toInt();
+    QImage result;
+    QSize partSize;
+    QSize resultSize;
+
+    // Get the current part index and set the index of the part to be rendered.
+    int currentPart = m_document->swapCurrentPart(partNumber);
+
+    // Get the size of the part
+    partSize = m_document->documentSize();
+    partSize.setHeight(Twips::convertTwipsToPixels(partSize.height()));
+    partSize.setWidth(Twips::convertTwipsToPixels(partSize.width()));
+
+    // Set the size of the rendered thumbnail
+    if (partSize.width() > partSize.height()) {
+        resultSize.setWidth(TILE_SIZE);
+        resultSize.setHeight(TILE_SIZE * partSize.height() / partSize.width());
+    } else {
+        resultSize.setHeight(TILE_SIZE);
+        resultSize.setWidth(TILE_SIZE * partSize.width() / partSize.height());
+    }
+
+    // Render the part to QImage
+    result = m_document->paintTile(resultSize, QRect(QPoint(0, 0), partSize));
+
+    // Re-set the earlier current part
+    m_document->swapCurrentPart(currentPart);
+
+    return result;
+
+}

=== added file 'src/plugin/libreofficetoolkit-qml-plugin/lopartsimageprovider.h'
--- src/plugin/libreofficetoolkit-qml-plugin/lopartsimageprovider.h	1970-01-01 00:00:00 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/lopartsimageprovider.h	2015-09-22 19:03:33 +0000
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef LOPARTSIMAGEPROVIDER_H
+#define LOPARTSIMAGEPROVIDER_H
+
+#include <QQuickImageProvider>
+
+class LODocument;
+
+class LOPartsImageProvider : public QQuickImageProvider
+{
+public:
+    LOPartsImageProvider(LODocument *document);
+    QImage requestImage(const QString & id, QSize * size, const QSize & requestedSize);
+
+private:
+    LODocument *m_document;
+};
+
+#endif // LOPARTSIMAGEPROVIDER_H

=== added file 'src/plugin/libreofficetoolkit-qml-plugin/lopartsmodel.cpp'
--- src/plugin/libreofficetoolkit-qml-plugin/lopartsmodel.cpp	1970-01-01 00:00:00 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/lopartsmodel.cpp	2015-09-22 19:03:33 +0000
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "lopartsmodel.h"
+#include "lodocument.h"
+#include "lopartsimageprovider.h"
+
+#include <QQmlContext>
+#include <QQmlEngine>
+#include <QDebug>
+
+LOPartsModel::LOPartsModel(QAbstractListModel *parent):
+    QAbstractListModel(parent)
+{   
+    connect(this, SIGNAL(documentChanged()), this, SLOT(fillModel()));
+}
+
+void LOPartsModel::setDocument(LODocument *document)
+{
+    if (m_document == document)
+        return;
+
+    m_document = document;
+    Q_EMIT documentChanged();
+
+    QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
+    QString imageProviderName = "lok";
+
+    if (engine->imageProvider(imageProviderName))
+        engine->removeImageProvider(imageProviderName);
+
+    engine->addImageProvider(imageProviderName, new LOPartsImageProvider(m_document));
+
+}
+
+QHash<int, QByteArray> LOPartsModel::roleNames() const
+{
+    QHash<int, QByteArray> roles;
+    roles[IndexRole] = "index";
+    roles[NameRole] = "name";
+
+    return roles;
+}
+
+int LOPartsModel::rowCount(const QModelIndex & parent) const
+{
+    Q_UNUSED(parent)
+    return m_entries.count();
+}
+
+QVariant LOPartsModel::data(const QModelIndex & index, int role) const
+{
+    if (index.row() < 0 || index.row() > m_entries.count())
+        return QVariant();
+
+    const LOPartEntry &part = m_entries.at(index.row());
+
+    switch (role) {
+    case IndexRole:
+        return part.index;
+    case NameRole:
+        return part.name;
+
+    default:
+        return 0;
+    }
+}
+
+QVariantMap LOPartsModel::get(int index) const
+{
+    if (index < 0 || index > m_entries.count() - 1) {
+        qWarning() << Q_FUNC_INFO << "Index not valid, return undefined";
+        return QVariantMap();
+    }
+
+    const LOPartEntry &part = m_entries.at(index);
+
+    QVariantMap map;
+    map["name"] = part.name;
+    map["index"] = part.index;
+
+    return map;
+}
+
+void LOPartsModel::fillModel() {
+    if (m_document) {
+        if (!m_entries.isEmpty()) {
+            beginRemoveRows(QModelIndex(), 0, rowCount());
+            m_entries.clear();
+            endRemoveRows();
+        }
+
+        int partsCount = m_document->partsCount();
+
+        for (int i = 0; i < partsCount; i++) {
+            LOPartEntry part;
+            part.index = i;
+            part.name = m_document->getPartName(i);
+
+            beginRemoveRows(QModelIndex(), rowCount(), rowCount());
+            m_entries.append(part);
+            endRemoveRows();
+        }
+
+        Q_EMIT countChanged();
+    }    
+}
+
+LOPartsModel::~LOPartsModel()
+{
+}

=== added file 'src/plugin/libreofficetoolkit-qml-plugin/lopartsmodel.h'
--- src/plugin/libreofficetoolkit-qml-plugin/lopartsmodel.h	1970-01-01 00:00:00 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/lopartsmodel.h	2015-09-22 19:03:33 +0000
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef LOPARTSMODEL_H
+#define LOPARTSMODEL_H
+
+#include <QAbstractListModel>
+
+class LODocument;
+
+class LOPartEntry
+{
+public:
+    QString name;
+    int index = 0;
+};
+
+class LOPartsModel : public QAbstractListModel
+{
+    Q_OBJECT
+    Q_DISABLE_COPY(LOPartsModel)
+    Q_PROPERTY(LODocument* document READ document WRITE setDocument NOTIFY documentChanged)
+    Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
+
+public:
+    enum Roles {
+        NameRole,
+        IndexRole
+    };
+
+    explicit LOPartsModel(QAbstractListModel *parent = 0);
+    ~LOPartsModel();
+
+    LODocument* document() { return m_document; }
+    void setDocument(LODocument* document);
+
+    QHash<int, QByteArray> roleNames() const;
+
+    int rowCount(const QModelIndex & parent = QModelIndex()) const;
+    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
+
+    Q_INVOKABLE QVariantMap get(int index) const;
+
+Q_SIGNALS:
+    void documentChanged();
+    void countChanged();
+
+private slots:
+    void fillModel();
+
+private:
+    LODocument* m_document;
+    QList<LOPartEntry> m_entries;
+};
+
+#endif // LOPARTSMODEL_H

=== modified file 'src/plugin/libreofficetoolkit-qml-plugin/loview.cpp'
--- src/plugin/libreofficetoolkit-qml-plugin/loview.cpp	2015-09-19 13:31:19 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/loview.cpp	2015-09-22 19:03:33 +0000
@@ -25,6 +25,13 @@
 #include <QTimer>
 #include <QtCore/qmath.h>
 
+static qreal zoomValueToFitWidth;
+
+static qreal getZoomToFitWidth(const qreal &width, int documentWidth)
+{
+    return qreal(width / Twips::convertTwipsToPixels(documentWidth, 1.0));
+}
+
 LOView::LOView(QQuickItem *parent)
     : QQuickItem(parent)
     , m_parentFlickable(nullptr)
@@ -72,8 +79,14 @@
 
 void LOView::initializeDocument(const QString &path)
 {
+    if (m_document.data())
+        m_document.data()->disconnect(this);
+
     m_document = QSharedPointer<LODocument>(new LODocument());
     m_document->setPath(path);
+
+    connect(m_document.data(), SIGNAL(currentPartChanged()), this, SLOT(invalidateAllTiles()));
+
     Q_EMIT documentChanged();
 }
 
@@ -83,22 +96,38 @@
     return m_document.data();
 }
 
-// Not used yet.
 qreal LOView::zoomFactor() const
 {
     return m_zoomFactor;
 }
 
-// Not used yet.
-void LOView::setZoomFactor(qreal zoom)
+void LOView::setZoomFactor(const qreal zoom)
 {
     if (m_zoomFactor == zoom)
         return;
 
     m_zoomFactor = zoom;
+
+   if (m_zoomFactor != zoomValueToFitWidth)
+        setZoomMode(LOView::Manual);
+
     Q_EMIT zoomFactorChanged();
 }
 
+LOView::ZoomMode LOView::zoomMode() const
+{
+    return m_zoomMode;
+}
+
+void LOView::setZoomMode(const ZoomMode zoomMode)
+{
+    if (m_zoomMode == zoomMode)
+        return;
+
+    m_zoomMode = zoomMode;
+    Q_EMIT zoomModeChanged();
+}
+
 int LOView::cacheBuffer() const
 {
     return m_cacheBuffer;
@@ -113,6 +142,37 @@
     Q_EMIT cacheBufferChanged();
 }
 
+void LOView::adjustZoomToWidth()
+ {
+    setZoomMode(LOView::FitToWidth);
+
+    zoomValueToFitWidth = getZoomToFitWidth(m_parentFlickable->width(),
+                                            m_document->documentSize().width());
+
+    setZoomFactor(zoomValueToFitWidth);
+    qDebug() << "Adjust zoom to width - value:" << zoomValueToFitWidth;
+ }
+
+bool LOView::updateZoomIfAutomatic()
+{
+    // This function is only used in LOView::updateVisibleRect()
+    // It returns a bool, so that we can stop the execution of that function,
+    // which will be triggered again when we'll automatically update the zoom value.
+    if (m_zoomMode == LOView::FitToWidth) {
+        zoomValueToFitWidth = getZoomToFitWidth(m_parentFlickable->width(),
+                                                m_document->documentSize().width());
+
+        if (m_zoomFactor != zoomValueToFitWidth) {
+            setZoomFactor(zoomValueToFitWidth);
+
+            qDebug() << "Adjust automatic zoom to width - value:" << zoomValueToFitWidth;
+            return true;
+        }
+    }
+
+    return false;
+}
+
 // Update the size of LOView, according to the size of the loaded document.
 void LOView::updateViewSize()
 {
@@ -121,9 +181,8 @@
 
     QSize docSize = m_document->documentSize();
 
-    // FIXME: Area may become too large, resulting in a black texture.
-    this->setWidth(Twips::convertTwipsToPixels(docSize.width()) * m_zoomFactor);
-    this->setHeight(Twips::convertTwipsToPixels(docSize.height()) * m_zoomFactor);
+    this->setWidth(Twips::convertTwipsToPixels(docSize.width(), m_zoomFactor));
+    this->setHeight(Twips::convertTwipsToPixels(docSize.height(), m_zoomFactor));
 
     // TODO: Consider to use connections to widthChanged and heightChanged
     this->updateVisibleRect();
@@ -137,6 +196,31 @@
     if (!m_parentFlickable)
         return;
 
+    // Changes in parentFlickable width/height trigger directly LOView::updateVisibleRect(),
+    // since they don't imply a change in the zoom factor - i.e. LOView::updateViewSize().
+    // Anyway, this class also handle an automatic zoom when the parentFlickable has been
+    // resized, so we need to take care of it.
+    // updateZoomIfAutomatic() returns a bool, which is true when the zoomFactor is
+    // set to a new value.
+    // If that happens, stop the execution of this function, since the change of
+    // zoomFactor will trigger the updateViewSize() function, which triggers this
+    // function again.
+    if (this->updateZoomIfAutomatic())
+        return;
+
+    // Check if current tiles have a different zoom value
+    if (!m_tiles.isEmpty()) {
+        SGTileItem* tile = m_tiles.first();
+
+        if (tile->zoomFactor() != m_zoomFactor) {
+            clearView();
+
+#ifdef DEBUG_VERBOSE
+            qDebug() << "Zoom value of tiles is different than the current zoom value. Erasing cache...";
+#endif
+        }
+    }
+
     // Update information about the visible area
     m_visibleArea.setRect(m_parentFlickable->property("contentX").toInt(),
                           m_parentFlickable->property("contentY").toInt(),
@@ -216,6 +300,13 @@
     }
 }
 
+// FIXME: Just for the moment. In zoom branch we have all we need :)
+void LOView::invalidateAllTiles()
+{
+    clearView();
+    updateViewSize();
+}
+
 void LOView::createTile(int index, QRect rect)
 {
     if (!m_tiles.contains(index)) {
@@ -223,13 +314,13 @@
         qDebug() << "Creating tile indexed as" << index;
 #endif
 
-        auto tile = new SGTileItem(rect, this);
+        auto tile = new SGTileItem(rect, m_zoomFactor, this);
         m_tiles.insert(index, tile);
-        RenderEngine::instance()->enqueueTask(m_document, rect, tile->id());
+        RenderEngine::instance()->enqueueTask(m_document, rect, m_zoomFactor, tile->id());
     }
 #ifdef DEBUG_VERBOSE
     else {
-        qDebug() << "tile" << x << "x" << y << "already exists";
+        qDebug() << "tile" << index << "already exists";
     }
 #endif
 }
@@ -251,6 +342,14 @@
     }
 }
 
+void LOView::clearView()
+{
+    for (auto i = m_tiles.begin(); i != m_tiles.end(); ++i)
+        RenderEngine::instance()->dequeueTask(i.value()->id());
+
+    m_tiles.clear();
+}
+
 LOView::~LOView()
 {
     disconnect(RenderEngine::instance(), SIGNAL(renderFinished(int,QImage)), this, SLOT(renderResultReceived(int,QImage)));

=== modified file 'src/plugin/libreofficetoolkit-qml-plugin/loview.h'
--- src/plugin/libreofficetoolkit-qml-plugin/loview.h	2015-09-19 14:00:41 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/loview.h	2015-09-22 19:03:33 +0000
@@ -30,17 +30,22 @@
 class LOView : public QQuickItem
 {
     Q_OBJECT
+    Q_ENUMS(ZoomMode)
     Q_PROPERTY(QQuickItem* parentFlickable READ parentFlickable WRITE setParentFlickable NOTIFY parentFlickableChanged)
     Q_PROPERTY(LODocument* document        READ document        /*WRITE setDocument*/    NOTIFY documentChanged)
-
-    // TODO: Implement zoom!
     Q_PROPERTY(qreal       zoomFactor      READ zoomFactor      WRITE setZoomFactor      NOTIFY zoomFactorChanged)
+    Q_PROPERTY(ZoomMode    zoomMode        READ zoomMode                                 NOTIFY zoomModeChanged)
     Q_PROPERTY(int         cacheBuffer     READ cacheBuffer     WRITE setCacheBuffer     NOTIFY cacheBufferChanged)
 
 public:
     LOView(QQuickItem *parent = 0);
     ~LOView();
 
+    enum ZoomMode {
+        FitToWidth,
+        Manual
+    };
+
     QQuickItem* parentFlickable() const;
     void        setParentFlickable(QQuickItem* flickable);
 
@@ -49,28 +54,36 @@
     LODocument* document() const;
 
     qreal       zoomFactor() const;
-    void        setZoomFactor(qreal zoom);
+    void        setZoomFactor(const qreal zoom);
+
+    ZoomMode    zoomMode() const;
 
     int         cacheBuffer() const;
     void        setCacheBuffer(int cacheBuffer);
 
+    Q_INVOKABLE void adjustZoomToWidth();
+
 Q_SIGNALS:
     void parentFlickableChanged();
     void documentChanged();
     void zoomFactorChanged();
+    void zoomModeChanged();
     void cacheBufferChanged();
 
 private Q_SLOTS:
     void updateViewSize();
     void updateVisibleRect();
     void scheduleVisibleRectUpdate();
+    void invalidateAllTiles();
     void renderResultReceived(int id, QImage img);
 
 private:
+
     QQuickItem*                 m_parentFlickable;
     QSharedPointer<LODocument>  m_document;
 
     qreal                       m_zoomFactor;
+    ZoomMode                    m_zoomMode;
     int                         m_cacheBuffer;
 
     QRect                       m_visibleArea;
@@ -82,6 +95,9 @@
 
     void generateTiles(int x1, int y1, int x2, int y2, int tilesPerWidth);
     void createTile(int index, QRect rect);
+    void setZoomMode(const ZoomMode zoomMode);
+    bool updateZoomIfAutomatic();
+    void clearView();
 };
 
 #endif // LOVIEW_H

=== modified file 'src/plugin/libreofficetoolkit-qml-plugin/plugin.cpp'
--- src/plugin/libreofficetoolkit-qml-plugin/plugin.cpp	2015-07-04 16:00:33 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/plugin.cpp	2015-09-22 19:03:33 +0000
@@ -21,6 +21,7 @@
 #include "plugin.h"
 #include "lodocument.h"
 #include "loview.h"
+#include "lopartsmodel.h"
 
 void LOPlugin::registerTypes(const char *uri)
 {
@@ -29,6 +30,7 @@
     //@uri DocumentViewer.LibreOffice
     qmlRegisterType<LODocument>(uri, 1, 0, "Document");
     qmlRegisterType<LOView>(uri, 1, 0, "View");
+    qmlRegisterType<LOPartsModel>(uri, 1, 0, "PartsModel");
 }
 
 void LOPlugin::initializeEngine(QQmlEngine *engine, const char *uri)

=== modified file 'src/plugin/libreofficetoolkit-qml-plugin/qml/Viewer.qml'
--- src/plugin/libreofficetoolkit-qml-plugin/qml/Viewer.qml	2015-09-18 11:18:17 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/qml/Viewer.qml	2015-09-22 19:03:33 +0000
@@ -24,18 +24,28 @@
     property alias zoomFactor:  view.zoomFactor
     property alias cacheBuffer: view.cacheBuffer
 
+    property alias zoomMode:    view.zoomMode
     property string documentPath: ""
 
+    function adjustZoomToWidth()
+    {
+        view.adjustZoomToWidth();
+    }
+
     onDocumentPathChanged: {
         if (documentPath)
             view.initializeDocument(documentPath)
     }
 
-    contentHeight: view.height * view.zoomFactor
-    contentWidth: view.width * view.zoomFactor
+    // zoomFactor is not used here to set contentSize, since it's all managed
+    // internally, in the LibreOffice.View component.
+    contentHeight: view.height
+    contentWidth: view.width
 
     boundsBehavior: Flickable.StopAtBounds
 
+    Component.onCompleted: adjustZoomToWidth()
+
     LibreOffice.View {
         id: view
 

=== modified file 'src/plugin/libreofficetoolkit-qml-plugin/renderengine.cpp'
--- src/plugin/libreofficetoolkit-qml-plugin/renderengine.cpp	2015-09-18 13:00:43 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/renderengine.cpp	2015-09-22 19:03:33 +0000
@@ -12,11 +12,11 @@
     m_idealThreadCount = itc == -1 ? DefaultIdealThreadCount : itc;
 }
 
-void RenderEngine::enqueueTask(const QSharedPointer<LODocument>& doc, const QRect& area, int id)
+void RenderEngine::enqueueTask(const QSharedPointer<LODocument>& doc, const QRect& area, const qreal &zoom, int id)
 {
     Q_ASSERT(doc != nullptr);
 
-    m_queue.enqueue(EngineTask(doc, area, id));
+    m_queue.enqueue(EngineTask(doc, area, zoom, id));
 
     doNextTask();
 }
@@ -50,7 +50,7 @@
     auto task = m_queue.dequeue();
 
     QtConcurrent::run( [=] {
-        QImage img = task.document->paintTile(task.area.size(), task.area);
+        QImage img = task.document->paintTile(task.area.size(), task.area, task.zoom);
         QMetaObject::invokeMethod(this, "internalRenderCallback", Q_ARG(int, task.id), Q_ARG(QImage, img));
     });
 }

=== modified file 'src/plugin/libreofficetoolkit-qml-plugin/renderengine.h'
--- src/plugin/libreofficetoolkit-qml-plugin/renderengine.h	2015-09-18 13:00:43 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/renderengine.h	2015-09-22 19:03:33 +0000
@@ -13,12 +13,14 @@
 {
     int id;
     QRect area;
+    qreal zoom;
     QSharedPointer<LODocument> document;
 
 public:
-    EngineTask(const QSharedPointer<LODocument>& d, const QRect& a, int i):
+    EngineTask(const QSharedPointer<LODocument>& d, const QRect& a, const qreal& z, int i):
     id(i),
     area(a),
+    zoom(z),
     document(d)
     { }
 };
@@ -34,7 +36,7 @@
     const int DefaultIdealThreadCount = 2;
 
 public:
-    void enqueueTask(const QSharedPointer<LODocument>& doc, const QRect& area, int id);
+    void enqueueTask(const QSharedPointer<LODocument>& doc, const QRect& area, const qreal& zoom, int id);
     void dequeueTask(int id);
 
     static RenderEngine* instance() {

=== modified file 'src/plugin/libreofficetoolkit-qml-plugin/sgtileitem.cpp'
--- src/plugin/libreofficetoolkit-qml-plugin/sgtileitem.cpp	2015-09-19 13:31:19 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/sgtileitem.cpp	2015-09-22 19:03:33 +0000
@@ -10,9 +10,10 @@
 
 int SGTileItem::s_idCounter = 0xDEAD0000;
 
-SGTileItem::SGTileItem(const QRect& area, QQuickItem *parent)
+SGTileItem::SGTileItem(const QRect& area, const qreal &zoom, QQuickItem *parent)
     : QQuickItem(parent)
     , m_area(area)
+    , m_zoomFactor(zoom)
     , m_id (s_idCounter++)
 {
     setFlag(ItemHasContents, true);

=== modified file 'src/plugin/libreofficetoolkit-qml-plugin/sgtileitem.h'
--- src/plugin/libreofficetoolkit-qml-plugin/sgtileitem.h	2015-09-18 12:02:42 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/sgtileitem.h	2015-09-22 19:03:33 +0000
@@ -14,12 +14,15 @@
 {
     Q_OBJECT
 public:
-    SGTileItem(const QRect& area, QQuickItem *parent = 0);
+    SGTileItem(const QRect& area, const qreal &zoom = 1.0, QQuickItem *parent = 0);
     ~SGTileItem();
 
     inline const QRect& area() { return m_area; }
     inline void setArea(const QRect& rect) { m_area = rect; }
 
+    inline const qreal& zoomFactor() const { return m_zoomFactor; }
+    inline void setZoomFactor(const qreal &zoom) { m_zoomFactor = zoom; }
+
     inline int id() { return m_id; }
     inline void setId(int id) { m_id = id; }
 
@@ -37,6 +40,7 @@
 
 private:
     QRect m_area;
+    qreal m_zoomFactor;
     QImage m_data;
     int m_id;
 


Follow ups