← Back to team overview

ubuntu-touch-coreapps-reviewers team mailing list archive

[Merge] lp:~mrqtros/ubuntu-docviewer-app/reboot-qsg-impress-support into lp:ubuntu-docviewer-app/reboot

 

Roman Shchekin has proposed merging lp:~mrqtros/ubuntu-docviewer-app/reboot-qsg-impress-support into lp:ubuntu-docviewer-app/reboot.

Commit message:
RenderEngine - impress support.

Requested reviews:
  Ubuntu Document Viewer Developers (ubuntu-docviewer-dev)

For more details, see:
https://code.launchpad.net/~mrqtros/ubuntu-docviewer-app/reboot-qsg-impress-support/+merge/273922

RenderEngine - impress support.
-- 
Your team Ubuntu Document Viewer Developers is requested to review the proposed merge of lp:~mrqtros/ubuntu-docviewer-app/reboot-qsg-impress-support into lp:ubuntu-docviewer-app/reboot.
=== modified file 'po/com.ubuntu.docviewer.pot'
--- po/com.ubuntu.docviewer.pot	2015-09-30 23:56:04 +0000
+++ po/com.ubuntu.docviewer.pot	2015-10-08 21:01:04 +0000
@@ -8,7 +8,11 @@
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
+<<<<<<< TREE
 "POT-Creation-Date: 2015-10-01 01:55+0200\n"
+=======
+"POT-Creation-Date: 2015-10-08 23:48+0300\n"
+>>>>>>> MERGE-SOURCE
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@xxxxxx>\n"
@@ -19,7 +23,11 @@
 "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
 
 #: ../src/app/docviewer-application.cpp:162
+<<<<<<< TREE
 #: /tmp/build-reboot-lok-goto-dialog-Desktop-Default/po/com.ubuntu.docviewer.desktop.in.in.h:1
+=======
+#: /home/qtros/dev/build-reboot-qsg-impress-support-Desktop-Default/po/com.ubuntu.docviewer.desktop.in.in.h:1
+>>>>>>> MERGE-SOURCE
 msgid "Document Viewer"
 msgstr ""
 
@@ -63,7 +71,11 @@
 #: ../src/app/qml/common/RejectedImportDialog.qml:38
 #: ../src/app/qml/documentPage/DocumentPageSelectionModeHeader.qml:32
 #: ../src/app/qml/documentPage/SortSettingsDialog.qml:53
+<<<<<<< TREE
 #: ../src/app/qml/loView/LOViewDefaultHeader.qml:77
+=======
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:79
+>>>>>>> MERGE-SOURCE
 #: ../src/app/qml/pdfView/PdfViewDefaultHeader.qml:61
 #: ../src/app/qml/textView/TextViewDefaultHeader.qml:61
 msgid "Close"
@@ -151,7 +163,10 @@
 
 #: ../src/app/qml/documentPage/DeleteFileDialog.qml:44
 #: ../src/app/qml/documentPage/DocumentPagePickModeHeader.qml:28
+<<<<<<< TREE
 #: ../src/app/qml/loView/LOViewGotoDialog.qml:54
+=======
+>>>>>>> MERGE-SOURCE
 #: ../src/app/qml/pdfView/PdfViewGotoDialog.qml:52
 msgid "Cancel"
 msgstr ""
@@ -254,7 +269,12 @@
 msgstr ""
 
 #: ../src/app/qml/documentPage/DocumentPageSearchHeader.qml:27
+<<<<<<< TREE
 #: ../src/app/qml/loView/LOViewDefaultHeader.qml:77
+=======
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:79
+#: ../src/app/qml/loView/LOViewPage.qml:191
+>>>>>>> MERGE-SOURCE
 #: ../src/app/qml/pdfView/PdfViewDefaultHeader.qml:61
 #: ../src/app/qml/textView/TextViewDefaultHeader.qml:61
 msgid "Back"
@@ -305,32 +325,71 @@
 msgid "Reverse order"
 msgstr ""
 
+<<<<<<< TREE
 #: ../src/app/qml/loView/LOViewDefaultHeader.qml:58
+=======
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:56
+#: ../src/app/qml/textView/TextView.qml:42
+msgid "Loading..."
+msgstr ""
+
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:60
+>>>>>>> MERGE-SOURCE
 msgid "LibreOffice text document"
 msgstr ""
 
+<<<<<<< TREE
 #: ../src/app/qml/loView/LOViewDefaultHeader.qml:60
+=======
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:62
+>>>>>>> MERGE-SOURCE
 msgid "LibreOffice spread sheet"
 msgstr ""
 
+<<<<<<< TREE
 #: ../src/app/qml/loView/LOViewDefaultHeader.qml:62
+=======
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:64
+>>>>>>> MERGE-SOURCE
 msgid "LibreOffice presentation"
 msgstr ""
 
+<<<<<<< TREE
 #: ../src/app/qml/loView/LOViewDefaultHeader.qml:64
+=======
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:66
+>>>>>>> MERGE-SOURCE
 msgid "LibreOffice Draw document"
 msgstr ""
 
+<<<<<<< TREE
 #: ../src/app/qml/loView/LOViewDefaultHeader.qml:66
+=======
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:68
+>>>>>>> MERGE-SOURCE
 msgid "Unknown LibreOffice document"
 msgstr ""
 
+<<<<<<< TREE
 #: ../src/app/qml/loView/LOViewDefaultHeader.qml:68
+=======
+#: ../src/app/qml/loView/LOViewDefaultHeader.qml:70
+>>>>>>> MERGE-SOURCE
 msgid "Unknown type document"
 msgstr ""
 
+<<<<<<< TREE
 #: ../src/app/qml/loView/LOViewDefaultHeader.qml:101
 msgid "Go to position..."
+=======
+#: ../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..."
+>>>>>>> MERGE-SOURCE
 msgstr ""
 
 #: ../src/app/qml/loView/LOViewDefaultHeader.qml:108
@@ -345,6 +404,7 @@
 msgid "Enable night mode"
 msgstr ""
 
+<<<<<<< TREE
 #: ../src/app/qml/loView/LOViewGotoDialog.qml:27
 msgid "Go to position"
 msgstr ""
@@ -356,6 +416,42 @@
 #: ../src/app/qml/loView/LOViewGotoDialog.qml:46
 #: ../src/app/qml/pdfView/PdfViewGotoDialog.qml:44
 msgid "GO!"
+=======
+#: ../src/app/qml/loView/LOViewPage.qml:34
+#: ../src/app/qml/loView/LOViewPage.qml:189
+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/SlideControllerPanel.qml:62
+#, qt-format
+msgid "Slide %1 of %2"
+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%"
+>>>>>>> MERGE-SOURCE
 msgstr ""
 
 #. TRANSLATORS: "Contents" refers to the "Table of Contents" of a PDF document.
@@ -375,6 +471,7 @@
 msgid "Page %1 of %2"
 msgstr ""
 
+<<<<<<< TREE
 #: ../src/app/qml/pdfView/PdfViewDefaultHeader.qml:85
 msgid "Go to page..."
 msgstr ""
@@ -390,6 +487,19 @@
 
 #: ../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!"
+>>>>>>> MERGE-SOURCE
 msgstr ""
 
 #. TRANSLATORS: This string is used for renaming a copied file,
@@ -406,6 +516,10 @@
 msgid "copy %1"
 msgstr ""
 
+<<<<<<< TREE
 #: /tmp/build-reboot-lok-goto-dialog-Desktop-Default/po/com.ubuntu.docviewer.desktop.in.in.h:2
+=======
+#: /home/qtros/dev/build-reboot-qsg-impress-support-Desktop-Default/po/com.ubuntu.docviewer.desktop.in.in.h:2
+>>>>>>> MERGE-SOURCE
 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-10-08 21:01:04 +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-10-08 21:01:04 +0000
@@ -0,0 +1,144 @@
+/*
+ * 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;
+
+    var view = loPageContentLoader.item.loView
+    var isPresentation = view.document.documentType === LO.Document.PresentationDocument
+
+    if (event.key == Qt.Key_PageUp) {
+        if (isPresentation)
+            view.document.currentPart -= 1
+        else
+            view.moveView("vertical", -view.height)
+ 
+        return;
+    }
+ 
+    if (event.key == Qt.Key_PageDown) {
+        if (isPresentation)
+            view.document.currentPart += 1
+        else
+            view.moveView("vertical", view.height)
+ 
+        return;
+    }
+ 
+    if (event.key == Qt.Key_Home) {
+        if (event.modifiers & Qt.ControlModifier) {
+            view.contentX = 0
+            view.contentY = 0
+            view.document.currentPart = 0
+        } else {
+            view.contentX = 0
+            view.contentY = 0
+        }
+    }
+
+    if (event.key == Qt.Key_End) {
+        if (event.modifiers & Qt.ControlModifier) {
+            view.contentX = view.contentWidth - view.width
+            view.contentY = view.contentHeight - view.height
+            console.log(view.document.currentPart, view.document.partsCount - 1)
+            view.document.currentPart = view.document.partsCount - 1
+        } else {
+            view.contentX = view.contentWidth - view.width
+            view.contentY = view.contentHeight - view.height
+        }
+    }
+
+    if (event.key == Qt.Key_Up) {
+        view.moveView("vertical", -pixelDiff)
+        return;
+    }
+ 
+    if (event.key == Qt.Key_Down) {
+        view.moveView("vertical", pixelDiff)
+        return;
+    }
+ 
+    if (event.key == Qt.Key_Left) {
+        view.moveView("horizontal", -pixelDiff)
+        return;
+    }
+ 
+    if (event.key == Qt.Key_Right) {
+        view.moveView("horizontal", pixelDiff)
+        return;
+    }
+
+    if (event.key == Qt.Key_Plus) {
+        if (event.modifiers & Qt.ControlModifier) {
+            view.zoomFactor = Math.max(4.0, view.zoomFactor + 0.25)
+        }
+    }
+
+    if (event.key == Qt.Key_Minus) {
+        if (event.modifiers & Qt.ControlModifier) {
+            view.zoomFactor = Math.min(0.5, view.zoomFactor - 0.25)
+        }
+    }
+
+
+    /*
+    if (event.key == Qt.Key_C) {
+        if (event.modifiers & Qt.ControlModifier) {
+
+        }
+    }
+    */
+
+    /*
+    if (event.key == Qt.Key_X) {
+        if (event.modifiers & Qt.ControlModifier) {
+
+        }
+    }
+    */
+
+    /*
+    if (event.key == Qt.Key_V) {
+        if (event.modifiers & Qt.ControlModifier) {
+
+        }
+    }
+    */
+
+    /*
+    if (event.key == Qt.Key_A) {
+        if (event.modifiers & Qt.ControlModifier) {
+
+        }
+    }
+    */
+
+    /*
+    if (event.key == Qt.Key_L) {
+        if (event.modifiers & Qt.ControlModifier) {
+            // TODO: Go to page dialog
+        }
+    }
+    */
+
+    /*
+    if (event.key == Qt.Key_Question) {
+        if (event.modifiers & (Qt.ControlModifier | Qt.ShiftModifier)) {
+            // TODO: Keyboard shortcuts
+        }
+    }
+    */
+}

=== modified file 'src/app/qml/loView/LOViewDefaultHeader.qml'
--- src/app/qml/loView/LOViewDefaultHeader.qml	2015-09-18 18:30:05 +0000
+++ src/app/qml/loView/LOViewDefaultHeader.qml	2015-10-08 21:01:04 +0000
@@ -53,7 +53,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:
@@ -89,18 +92,22 @@
 
     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"
+<<<<<<< TREE
             text: i18n.tr("Go to position...")
             onTriggered: PopupUtils.open(Qt.resolvedUrl("LOViewGotoDialog.qml"), targetPage)
             visible: loDocument.documentType == LibreOffice.Document.TextDocument
+=======
+            text: i18n.tr("Go to page...")
+            enabled: false
+>>>>>>> MERGE-SOURCE
         },
 
         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();
-    }
-}

=== renamed file 'src/app/qml/loView/LOViewGotoDialog.qml' => 'src/app/qml/loView/LOViewGotoDialog.qml.THIS'
=== 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-10-08 21:01:04 +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)
+
+    bottomEdgeTitle: i18n.tr("Slides")
+    bottomEdgeEnabled: {
+        if (!loPageContentLoader.loaded)
+            return false
+
+        // else
+        return loPageContentLoader.item.loDocument.documentType == LO.Document.PresentationDocument && !wideWindow
+    }
+
     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
     }
@@ -45,33 +70,152 @@
     Component {
         id: loPageContentComponent
 
-        Item {
+        FocusScope {
+            id: loPageContent
+            anchors.fill: parent
+
             property alias loDocument: loView.document
+            property alias loView: loView
+            property alias bottomEdgePartsPage: bottomEdgePartsPage
 
-            LO.Viewer {
-                id: loView
-                objectName: "loView"
+            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: loView.partsModel //LO.PartsModel { document: loPageContent.loDocument }
+                                visible: /*loView.partsModel &&*/ loDocument.documentType == LO.Document.PresentationDocument
+                                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
+
+                                    // Keyboard events
+                                    focus: true
+                                    Keys.onPressed: KeybHelper.parseEvent(event)
+                                    Component.onCompleted: loPageContent.forceActiveFocus()
+                                }
+                            }
+
+                            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
+
+                    // Keyboard events
+                    focus: true
+                    Keys.onPressed: KeybHelper.parseEvent(event)
+
+                    Component.onCompleted: {
+                        // WORKAROUND: Fix for wrong grid unit size
+                        flickDeceleration = 1500 * units.gridUnit / 8
+                        maximumFlickVelocity = 2500 * units.gridUnit / 8
+                        loPageContent.forceActiveFocus()
+                    }
+
+                    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 {
+                        property bool belongsToNestedPage: true
+                        anchors.fill: parent
+                        model: loView.partsModel //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-10-08 21:01:04 +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-10-08 21:01:04 +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-10-08 21:01:04 +0000
@@ -0,0 +1,89 @@
+/*
+ * 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 ? loView.document.currentPart : -1
+    highlightMoveDuration: UbuntuAnimation.SnapDuration
+
+    delegate: ListItemWithActions {
+        id: delegate
+
+        width: parent.width
+        height: units.gu(16)
+
+        color: (loView.document.currentPart === model.index) ? Theme.palette.selected.background
+                                                                 : "transparent"
+
+        AbstractButton {
+            objectName: "abstractbutton"
+            anchors.fill: parent
+
+            onClicked: {
+                loView.document.currentPart = model.index
+
+                // Check if the view has been included in a nested page (e.g.
+                // bottomEdge). If so, close that page and return to the
+                // main viewer.
+                if (view.hasOwnProperty("belongsToNestedPage"))
+                    pageStack.pop();
+            }
+        }
+
+        RowLayout {
+            anchors.fill: parent
+            spacing: units.gu(1)
+
+            Image {
+                Layout.fillHeight: true
+                Layout.preferredWidth: height
+                fillMode: Image.PreserveAspectFit
+                // Do not store a cache of the thumbnail, so that we don't show
+                // thumbnails of a previously loaded document.
+                cache: true // TODO PLAY WITH IT
+                source: model.thumbnail
+            }
+
+            Label {
+                Layout.fillWidth: true
+                wrapMode: Text.WordWrap
+                text: model.name
+                color: (loView.document.currentPart === model.index) ? UbuntuColors.orange
+                                                                         : Theme.palette.selected.backgroundText
+            }
+
+            Label {
+                text: model.index + 1
+                color: (loView.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-10-08 21:01:04 +0000
@@ -0,0 +1,87 @@
+/*
+ * 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.loView.goFirstPart()
+
+            Icon {
+                anchors.centerIn: parent
+                width: units.gu(2.5); height: width
+                name: "go-first"
+            }
+        }
+
+        AbstractButton {
+            width: units.gu(4); height: parent.height
+            onClicked: loPageContentLoader.item.loView.goPreviousPart()
+
+            Icon {
+                anchors.centerIn: parent
+                width: units.gu(2.5); height: width
+                name: "go-previous"
+            }
+        }
+
+        Label {
+            text: i18n.tr("Slide %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.loView.goNextPart()
+
+            Icon {
+                anchors.centerIn: parent
+                width: units.gu(2.5); height: width
+                name: "go-next"
+            }
+        }
+
+        AbstractButton {
+            width: units.gu(4); height: parent.height
+            onClicked: loPageContentLoader.item.loView.goLastPart()
+
+            Icon {
+                anchors.centerIn: parent
+                width: units.gu(2.5); height: width
+                name: "go-last"
+            }
+        }
+    }
+}

=== 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-10-08 21:01:04 +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/app/qml/ubuntu-docviewer-app.qml'
--- src/app/qml/ubuntu-docviewer-app.qml	2015-09-11 14:48:57 +0000
+++ src/app/qml/ubuntu-docviewer-app.qml	2015-10-08 21:01:04 +0000
@@ -37,7 +37,7 @@
     useDeprecatedToolbar: false   
     automaticOrientation: true
 
-    width: units.gu(50)
+    width: units.gu(150)
     height: units.gu(75)
 
     function openDocument(path)  {

=== 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-10-08 21:01:04 +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-10-08 21:01:04 +0000
@@ -36,6 +36,7 @@
 
 LODocument::LODocument()
   : m_path("")
+  , m_currentPart(-1)
   , m_document(nullptr)
 {
     // This space is intentionally empty.
@@ -60,6 +61,22 @@
     this->loadDocument(m_path);
 }
 
+int LODocument::currentPart() {
+    return m_currentPart;
+}
+
+void LODocument::setCurrentPart(int index)
+{
+    if (!m_document)
+        return;
+
+    if (m_currentPart == index || index < 0 || index > partsCount() - 1)
+        return;
+
+    m_currentPart = index;
+    Q_EMIT currentPartChanged();
+}
+
 // Load the document
 bool LODocument::loadDocument(const QString &pathName)
 {
@@ -78,6 +95,8 @@
     m_docType = DocumentType(m_document->getDocumentType());
     Q_EMIT documentTypeChanged();
 
+    setCurrentPart(m_document->getPart());
+
     m_document->initializeForRendering();
     qDebug() << "Document loaded successfully !";
 
@@ -91,6 +110,17 @@
     return m_docType;
 }
 
+int LODocument::documentPart() const
+{
+    return m_document->getPart();
+}
+
+void LODocument::setDocumentPart(int p)
+{
+    if (documentPart() != p)
+        m_document->setPart(p);
+}
+
 // Return the size of the document, in TWIPs
 QSize LODocument::documentSize() const
 {
@@ -106,8 +136,14 @@
 
 // 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)
 {
+    if (!m_document)
+        return QImage();
+
+//    if (m_currentPart != m_document->getPart())
+//        m_document->setPart(m_currentPart);
+
     QImage result = QImage(canvasSize.width(), canvasSize.height(),  QImage::Format_RGB32);
 
 #ifdef DEBUG_TILE_BENCHMARK
@@ -117,10 +153,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 +165,56 @@
     return result.rgbSwapped();
 }
 
+QImage LODocument::paintThumbnail(qreal size)
+{
+    if (!m_document)
+        return QImage();
+
+#ifdef DEBUG_TILE_BENCHMARK
+    QElapsedTimer renderTimer;
+    renderTimer.start();
+#endif
+
+    qreal tWidth = this->documentSize().width();
+    qreal tHeight = this->documentSize().height();
+
+    QSize resultSize;
+
+    if (tWidth > tHeight) {
+        resultSize.setWidth(size);
+        resultSize.setHeight(size * tHeight / tWidth);
+    } else {
+        resultSize.setHeight(size);
+        resultSize.setWidth(size * tWidth / tHeight);
+    }
+
+    QImage result = QImage(resultSize.width(), resultSize.height(), QImage::Format_RGB32);
+    m_document->paintTile(result.bits(), resultSize.width(), resultSize.height(),
+                          0, 0, tWidth, tHeight);
+
+#ifdef DEBUG_TILE_BENCHMARK
+    qDebug() << "Time to render the thumbnail:" << renderTimer.elapsed() << "ms";
+#endif
+
+    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::fromUtf8(m_document->getPartName(index));
+}
+ 
 /* 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-10-08 21:01:04 +0000
@@ -30,8 +30,12 @@
     Q_OBJECT
     Q_DISABLE_COPY(LODocument)
 
-    Q_PROPERTY(QString      path         READ path         WRITE setPath        NOTIFY pathChanged)
-    Q_PROPERTY(DocumentType documentType READ documentType                      NOTIFY documentTypeChanged)
+    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(int          documentPart READ documentPart WRITE setDocumentPart NOTIFY documentPartChanged)
+    Q_PROPERTY(DocumentType documentType READ documentType                       NOTIFY documentTypeChanged)
     Q_ENUMS(DocumentType)
 
 public:
@@ -49,19 +53,34 @@
     QString path() const;
     void setPath(const QString& pathName);
 
+    int currentPart();
+    void setCurrentPart(int index);
+
     DocumentType documentType() const;
 
+    int documentPart() const;
+    void setDocumentPart(int p);
+
     QSize documentSize() const;
-    QImage paintTile(const QSize& canvasSize, const QRect& tileSize);
+
+    QImage paintTile(const QSize& canvasSize, const QRect& tileSize, const qreal& zoom = 1.0);
+    QImage paintThumbnail(qreal size);
+
+    int partsCount();
+    QString getPartName(int index) const;
+    void setPart(int index);
 
     Q_INVOKABLE bool saveAs(QString url, QString format, QString filterOptions);
 
 Q_SIGNALS:
     void pathChanged();
+    void currentPartChanged();
     void documentTypeChanged();
+    void documentPartChanged();
 
 private:
     QString m_path;
+    int m_currentPart;
     DocumentType m_docType;
 
     bool loadDocument(const QString &pathNAme);

=== 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-10-08 21:01:04 +0000
@@ -0,0 +1,54 @@
+/*
+ * 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 "renderengine.h"
+
+LOPartsImageProvider::LOPartsImageProvider(const QSharedPointer<LODocument>& d)
+    : QQuickImageProvider(QQuickImageProvider::Image),
+      m_document(d)
+{ }
+
+QImage LOPartsImageProvider::requestImage(const QString & id, QSize * size, const QSize & requestedSize)
+{
+    Q_UNUSED(size)
+
+    QString type = id.section("/", 0, 0);
+
+    if (requestedSize.isNull() || type != "part" ||
+            m_document->documentType() != LODocument::PresentationDocument)
+        return QImage();
+
+    // Get info from "id".
+    int partNumber = id.section("/", 1, 1).toInt();
+    int itemId = id.section("/", 2, 2).toInt();
+
+    // Once rendered images can be found in hash.
+    if (m_images.contains(itemId))
+        return m_images[itemId];
+
+    const int defaultSize = 256;
+
+    RenderEngine::instance()->enqueueTask(m_document, partNumber, defaultSize, itemId);
+
+    // Return default image (empty).
+    static QImage defaultImage;
+    if (defaultImage.isNull())
+        defaultImage = QImage(defaultSize, defaultSize, QImage::Format_ARGB32);
+
+    return defaultImage;
+}

=== 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-10-08 21:01:04 +0000
@@ -0,0 +1,40 @@
+/*
+ * 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>
+#include <QSharedPointer>
+#include <QHash>
+#include <QDebug>
+
+class LODocument;
+
+class LOPartsImageProvider : public QQuickImageProvider
+{
+public:
+    LOPartsImageProvider(const QSharedPointer<LODocument>& d);
+    QImage requestImage(const QString & id, QSize * size, const QSize & requestedSize);
+
+    QHash<int, QImage> m_images;
+
+private:
+    QSharedPointer<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-10-08 21:01:04 +0000
@@ -0,0 +1,130 @@
+/*
+ * 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(const QSharedPointer<LODocument>& document, QAbstractListModel *parent):
+    QAbstractListModel(parent)
+{   
+    m_document = document;
+    fillModel();
+}
+
+QHash<int, QByteArray> LOPartsModel::roleNames() const
+{
+    QHash<int, QByteArray> roles;
+    roles[IndexRole] = "index";
+    roles[NameRole] = "name";
+    roles[IdRole] = "id";
+    roles[ThumbnailRole] = "thumbnail";
+
+    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;
+    case IdRole:
+        return part.id;
+    case ThumbnailRole:
+        return part.thumbnail;
+
+    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;
+    map["id"] = part.id;
+    map["thumbnail"] = part.thumbnail;
+
+    return map;
+}
+
+void LOPartsModel::notifyAboutChanges(int id)
+{
+    for (int i = 0; i < m_entries.size(); i++)
+        if (m_entries[i].id == id) {
+            m_entries[i].thumbnail += "/cached";
+            Q_EMIT dataChanged(createIndex(i, 0), createIndex(i + 1, 0));
+            break;
+        }
+}
+
+void LOPartsModel::fillModel() {
+    if (!m_document)
+        return;
+
+    if (!m_entries.isEmpty()) {
+        beginRemoveRows(QModelIndex(), 0, rowCount());
+        m_entries.clear();
+        endRemoveRows();
+    }
+
+    int partsCount = m_document->partsCount();
+    beginInsertColumns(QModelIndex(), 0, qMax(partsCount - 1, 0));
+    for (int i = 0; i < partsCount; i++) {
+        LOPartEntry part;
+
+        part.index = i;
+        part.name = m_document->getPartName(i);
+        part.id = RenderEngine::getNextId();
+        part.thumbnail = QString("image://lok/part/%1/%2").arg(QString::number(part.index)).arg(QString::number(part.id));
+
+        m_entries.append(part);
+    }
+    endInsertColumns();
+
+    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-10-08 21:01:04 +0000
@@ -0,0 +1,87 @@
+/*
+ * 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>
+#include <QImage>
+#include <QHash>
+#include <QSharedPointer>
+
+#include "renderengine.h"
+
+class LODocument;
+
+class LOPartEntry
+{
+public:
+    LOPartEntry():
+        index(0)
+    {
+        id = RenderEngine::getNextId();
+    }
+
+    int id;
+    QString name;
+    int index;
+    QString thumbnail;
+};
+
+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 = Qt::UserRole + 1,
+        IndexRole,
+        IdRole,
+        ThumbnailRole
+    };
+
+    explicit LOPartsModel(const QSharedPointer<LODocument>& document, 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;
+
+    void notifyAboutChanges(int id);
+
+Q_SIGNALS:
+    // void documentChanged();
+    void countChanged();
+
+private slots:
+    void fillModel();
+
+private:
+    QSharedPointer<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-10-08 21:01:04 +0000
@@ -25,10 +25,18 @@
 #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)
     , m_document(nullptr)
+    , m_partsModel(nullptr)
     , m_zoomFactor(1.0)
     , m_cacheBuffer(TILE_SIZE * 3)
     , m_visibleArea(0, 0, 0, 0)
@@ -41,7 +49,11 @@
     connect(this, SIGNAL(parentFlickableChanged()), this, SLOT(updateVisibleRect()));
     connect(this, SIGNAL(cacheBufferChanged()), this, SLOT(updateVisibleRect()));
     connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateVisibleRect()));
-    connect(RenderEngine::instance(), SIGNAL(renderFinished(int,QImage)), this, SLOT(renderResultReceived(int,QImage)));
+
+    connect(RenderEngine::instance(), SIGNAL(renderFinished(int,QImage)),
+            this, SLOT(slotTileRenderFinished(int,QImage)));
+    connect(RenderEngine::instance(), SIGNAL(thumbnailRenderFinished(int,QImage)),
+            this, SLOT(slotThumbnailRenderFinished(int,QImage)));
 }
 
 // Returns the parent QML Flickable
@@ -72,8 +84,27 @@
 
 void LOView::initializeDocument(const QString &path)
 {
+    if (m_document)
+        m_document->disconnect(this);
+
     m_document = QSharedPointer<LODocument>(new LODocument());
     m_document->setPath(path);
+
+    // TODO MOVE
+    m_partsModel = new LOPartsModel(m_document);
+    Q_EMIT partsModelChanged();
+
+    // --------------------------------------------------
+    QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
+    if (engine->imageProvider("lok"))
+        engine->removeImageProvider("lok");
+
+    m_imageProvider = new LOPartsImageProvider(m_document);
+    engine->addImageProvider("lok", m_imageProvider);
+    // --------------------------------------------------
+
+    connect(m_document.data(), SIGNAL(currentPartChanged()), this, SLOT(invalidateAllTiles()));
+
     Q_EMIT documentChanged();
 }
 
@@ -83,22 +114,43 @@
     return m_document.data();
 }
 
-// Not used yet.
+LOPartsModel *LOView::partsModel() const
+{
+    return m_partsModel;
+}
+
 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 +165,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 +204,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 +219,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 +323,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 +337,13 @@
         qDebug() << "Creating tile indexed as" << index;
 #endif
 
-        auto tile = new SGTileItem(rect, this);
+        auto tile = new SGTileItem(rect, m_zoomFactor, RenderEngine::getNextId(), this);
         m_tiles.insert(index, tile);
-        RenderEngine::instance()->enqueueTask(m_document, rect, tile->id());
+        RenderEngine::instance()->enqueueTask(m_document, m_document->currentPart(), rect, m_zoomFactor, tile->id());
     }
 #ifdef DEBUG_VERBOSE
     else {
-        qDebug() << "tile" << x << "x" << y << "already exists";
+        qDebug() << "tile" << index << "already exists";
     }
 #endif
 }
@@ -240,7 +354,7 @@
         m_updateTimer.start(20);
 }
 
-void LOView::renderResultReceived(int id, QImage img)
+void LOView::slotTileRenderFinished(int id, QImage img)
 {
     for (auto i = m_tiles.begin(); i != m_tiles.end(); ++i) {
         SGTileItem* sgtile = i.value();
@@ -251,9 +365,29 @@
     }
 }
 
+void LOView::slotThumbnailRenderFinished(int id, QImage img)
+{
+    if (!m_imageProvider->m_images.contains(id))
+        m_imageProvider->m_images.insert(id, img);
+    m_partsModel->notifyAboutChanges(id);
+}
+
+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)));
+    delete m_partsModel;
+
+    disconnect(RenderEngine::instance(), SIGNAL(renderFinished(int,QImage)),
+               this, SLOT(slotTileRenderFinished(int,QImage)));
+    disconnect(RenderEngine::instance(), SIGNAL(thumbnailRenderFinished(int,QImage)),
+               this, SLOT(slotThumbnailRenderFinished(int,QImage)));
 
     // Remove all tasks from rendering queue.
     for (auto i = m_tiles.begin(); i != m_tiles.end(); ++i)

=== 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-10-08 21:01:04 +0000
@@ -21,8 +21,12 @@
 #include <QQuickItem>
 #include <QTimer>
 #include <QSharedPointer>
+#include <QQmlContext>
+#include <QQmlEngine>
 
 #include "renderengine.h"
+#include "lopartsmodel.h"
+#include "lopartsimageprovider.h"
 
 class LODocument;
 class SGTileItem;
@@ -30,47 +34,67 @@
 class LOView : public QQuickItem
 {
     Q_OBJECT
-    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(int         cacheBuffer     READ cacheBuffer     WRITE setCacheBuffer     NOTIFY cacheBufferChanged)
+    Q_ENUMS(ZoomMode)
+    Q_PROPERTY(QQuickItem*   parentFlickable READ parentFlickable WRITE setParentFlickable NOTIFY parentFlickableChanged)
+    Q_PROPERTY(LODocument*   document        READ document        /*WRITE setDocument*/    NOTIFY documentChanged)
+    Q_PROPERTY(LOPartsModel* partsModel      READ partsModel                               NOTIFY partsModelChanged)
+    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);
 
     Q_INVOKABLE void initializeDocument(const QString& path);
 
     LODocument* document() const;
+    LOPartsModel* partsModel() 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 partsModelChanged();
     void zoomFactorChanged();
+    void zoomModeChanged();
     void cacheBufferChanged();
 
 private Q_SLOTS:
     void updateViewSize();
     void updateVisibleRect();
     void scheduleVisibleRectUpdate();
-    void renderResultReceived(int id, QImage img);
+    void invalidateAllTiles();
+
+    void slotTileRenderFinished(int id, QImage img);
+    void slotThumbnailRenderFinished(int id, QImage img);
 
 private:
+
     QQuickItem*                 m_parentFlickable;
     QSharedPointer<LODocument>  m_document;
+    LOPartsModel*               m_partsModel; // TODO MB move to document.
+    LOPartsImageProvider*       m_imageProvider; // The QQmlEngine takes ownership of provider.
 
     qreal                       m_zoomFactor;
+    ZoomMode                    m_zoomMode;
     int                         m_cacheBuffer;
 
     QRect                       m_visibleArea;
@@ -82,6 +106,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-10-08 21:01:04 +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");
+    qmlRegisterUncreatableType<LOPartsModel>(uri, 1, 0, "PartsModel", "You shouldn't create LOPartsModel in QML");
 }
 
 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-10-08 21:01:04 +0000
@@ -23,22 +23,74 @@
     property alias document:    view.document
     property alias zoomFactor:  view.zoomFactor
     property alias cacheBuffer: view.cacheBuffer
+    property alias partsModel: view.partsModel
+    property alias zoomMode:    view.zoomMode
 
     property string documentPath: ""
 
+    function adjustZoomToWidth()
+    {
+        view.adjustZoomToWidth();
+    }
+
+    function moveView(axis, diff)
+    {
+        if (axis == "vertical") {
+            var maxContentY = Math.max(0, rootFlickable.contentHeight - rootFlickable.height)
+            rootFlickable.contentY = Math.max(0, Math.min(rootFlickable.contentY + diff, maxContentY ))
+        } else {
+            var maxContentX = Math.max(0, rootFlickable.contentWidth - rootFlickable.width)
+            rootFlickable.contentX = Math.max(0, Math.min(rootFlickable.contentX + diff, maxContentX ))
+        }
+    }
+
+    function goNextPart()
+    {
+        document.currentPart = Math.min(document.currentPart + 1, document.partsCount - 1)
+    }
+
+    function goPreviousPart()
+    {
+        document.currentPart = Math.max(0, document.currentPart - 1)
+    }
+
+    function goFirstPart()
+    {
+        document.currentPart = 0
+    }
+
+    function goLastPart()
+    {
+        document.currentPart = document.partsCount - 1
+    }
+
     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
 
         parentFlickable: rootFlickable
     }
+
+    Connections {
+        target: view.document
+
+        onCurrentPartChanged: {
+            // Position view at top-left corner
+            rootFlickable.contentX = 0
+            rootFlickable.contentY = 0
+        }
+    }
 }

=== 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-10-08 21:01:04 +0000
@@ -6,17 +6,30 @@
 
 RenderEngine::RenderEngine():
     QObject(nullptr),
-    m_activeTaskCount(0)
+    m_activeTaskCount(0),
+    m_enabled(true),
+    m_lastPart(-1)
 {
     int itc = QThread::idealThreadCount();
     m_idealThreadCount = itc == -1 ? DefaultIdealThreadCount : itc;
-}
-
-void RenderEngine::enqueueTask(const QSharedPointer<LODocument>& doc, const QRect& area, int id)
-{
-    Q_ASSERT(doc != nullptr);
-
-    m_queue.enqueue(EngineTask(doc, area, id));
+
+    connect(this, SIGNAL(enabledChanged()), this, SLOT(doNextTask()));
+}
+
+void RenderEngine::enqueueTask(const QSharedPointer<LODocument>& doc, int part, const QRect& area, const qreal &zoom, int id)
+{
+    Q_ASSERT(doc != nullptr);
+
+    m_queue.enqueue(EngineTask(doc, part, area, zoom, id));
+
+    doNextTask();
+}
+
+void RenderEngine::enqueueTask(const QSharedPointer<LODocument> &doc, int part, qreal size, int id)
+{
+    Q_ASSERT(doc != nullptr);
+
+    m_queue.enqueue(EngineTask(doc, part, size, id));
 
     doNextTask();
 }
@@ -30,10 +43,12 @@
         }
 }
 
-void RenderEngine::internalRenderCallback(int id, QImage img)
+void RenderEngine::internalRenderCallback(int id, QImage img, bool isThumbnail)
 {
     m_activeTaskCount--;
-    Q_EMIT renderFinished(id, img);
+    if (isThumbnail)
+        Q_EMIT thumbnailRenderFinished(id, img);
+    else Q_EMIT renderFinished(id, img);
     doNextTask();
 }
 
@@ -43,14 +58,36 @@
     qDebug() << " ---- doNextTask" << m_activeTaskCount << m_queue.count();
 #endif
 
-    if (m_activeTaskCount >= m_idealThreadCount || !m_queue.count())
+    // Check for too much threads or empty queue.
+    if (m_activeTaskCount >= m_idealThreadCount || !m_queue.count() || !m_enabled)
+        return;
+
+    // We should avoid different part rendering in the same time.
+    if (m_activeTaskCount && m_queue.head().part != m_lastPart)
         return;
 
     m_activeTaskCount++;
-    auto task = m_queue.dequeue();
+    EngineTask task = m_queue.dequeue();
+
+    // Set correct part.
+    m_lastPart = task.part;
+    task.document->setDocumentPart(m_lastPart);
 
     QtConcurrent::run( [=] {
-        QImage img = task.document->paintTile(task.area.size(), task.area);
-        QMetaObject::invokeMethod(this, "internalRenderCallback", Q_ARG(int, task.id), Q_ARG(QImage, img));
+        if (task.isThumbnail) {
+            QImage img = task.document->paintThumbnail(task.size);
+            QMetaObject::invokeMethod(this, "internalRenderCallback",
+                                      Q_ARG(int, task.id), Q_ARG(QImage, img), Q_ARG(bool, task.isThumbnail));
+        } else {
+            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), Q_ARG(bool, task.isThumbnail));
+        }
+//        QImage img = task.isThumbnail ?
+//                    //task.document->paintThumbnail(task.size) :
+//                    task.document->paintTile(task.area.size(), task.area, task.zoom) :
+//                    task.document->paintTile(task.area.size(), task.area, task.zoom);
+//        QMetaObject::invokeMethod(this, "internalRenderCallback",
+//                                  Q_ARG(int, task.id), Q_ARG(QImage, img), Q_ARG(bool, task.isThumbnail));
     });
 }

=== 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-10-08 21:01:04 +0000
@@ -6,20 +6,46 @@
 #include <QSharedPointer>
 #include <QHash>
 #include <QQueue>
+#include <QAtomicInt>
 
 #include "lodocument.h"
 
+// TODO replace with class.
+
+
+// TODO Need more OOP here.
 struct EngineTask
 {
     int id;
+    int part;
+    QSharedPointer<LODocument> document;
+    // Used in thumbnail rendering.
+    qreal size;
+    // Used in tile rendering.
     QRect area;
-    QSharedPointer<LODocument> document;
-
+    qreal zoom;
+    // Internal.
+    bool isThumbnail;
 public:
-    EngineTask(const QSharedPointer<LODocument>& d, const QRect& a, int i):
-    id(i),
-    area(a),
-    document(d)
+
+    EngineTask(const QSharedPointer<LODocument>& d, int p, const QRect& a, const qreal& z, int i):
+        id(i),
+        part(p),
+        document(d),
+        size(0),
+        area(a),
+        zoom(z),
+        isThumbnail(false)
+    { }
+
+    EngineTask(const QSharedPointer<LODocument>& d, int p, qreal s, int i):
+        id(i),
+        part(p),
+        document(d),
+        size(s),
+        area(),
+        zoom(0),
+        isThumbnail(true)
     { }
 };
 
@@ -34,7 +60,8 @@
     const int DefaultIdealThreadCount = 2;
 
 public:
-    void enqueueTask(const QSharedPointer<LODocument>& doc, const QRect& area, int id);
+    void enqueueTask(const QSharedPointer<LODocument>& doc, int part, const QRect& area, const qreal& zoom, int id);
+    void enqueueTask(const QSharedPointer<LODocument>& doc, int part, qreal size, int id);
     void dequeueTask(int id);
 
     static RenderEngine* instance() {
@@ -43,17 +70,29 @@
         return s_instance;
     }
 
+    static int getNextId() {
+        static int idCounter = 0xDEAD0000;
+        return idCounter++;
+    }
+
 Q_SIGNALS:
     void renderFinished(int id, QImage img);
+    void thumbnailRenderFinished(int id, QImage img);
+    void enabledChanged();
 
 private:
-    Q_INVOKABLE void internalRenderCallback(int id, QImage img);
+    Q_INVOKABLE void internalRenderCallback(int id, QImage img, bool isThumbnail);
+
+private slots:
     void doNextTask();
 
 private:
     QQueue<EngineTask> m_queue;
     int m_activeTaskCount;
     int m_idealThreadCount;
+    int m_lastPart;
+
+    QAtomicInt m_enabled;
 };
 
 #endif // RENDERENGINE_H

=== 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-10-08 21:01:04 +0000
@@ -8,12 +8,13 @@
 #include <QSGFlatColorMaterial>
 #endif
 
-int SGTileItem::s_idCounter = 0xDEAD0000;
+//int SGTileItem::s_idCounter = 0xDEAD0000;
 
-SGTileItem::SGTileItem(const QRect& area, QQuickItem *parent)
+SGTileItem::SGTileItem(const QRect& area, qreal zoom, int id, QQuickItem *parent)
     : QQuickItem(parent)
     , m_area(area)
-    , m_id (s_idCounter++)
+    , m_zoomFactor(zoom)
+    , m_id (id)
 {
     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-10-08 21:01:04 +0000
@@ -14,12 +14,15 @@
 {
     Q_OBJECT
 public:
-    SGTileItem(const QRect& area, QQuickItem *parent = 0);
+    SGTileItem(const QRect& area, qreal zoom, int id, 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,10 +40,11 @@
 
 private:
     QRect m_area;
+    qreal m_zoomFactor;
     QImage m_data;
     int m_id;
 
-    static int s_idCounter;
+    // static int s_idCounter;
 };
 
 #endif // SGTILEITEM_H


Follow ups