← Back to team overview

ubuntu-touch-coreapps-reviewers team mailing list archive

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

 

Stefano Verzegnassi has proposed merging lp:~verzegnassi-stefano/ubuntu-docviewer-app/reboot-qsg-impress-support into lp:ubuntu-docviewer-app/reboot with lp:~verzegnassi-stefano/ubuntu-docviewer-app/reboot-lok-qsg-zoom as a prerequisite.

Commit message:
* [loviewer] Improved support for presentation document type
* [loviewer] Added keyboard shortcuts.
* [loviewer] Added an image provider for slides thumbnails, sync'd with RenderEngine
* [loviewer] Conditional layout for the presentation view: use a bottom edge or a sidebar to show the list of slides
* [loviewer] Moved zoom controls into a separate page head
* Updated translation template

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

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

* [loviewer] Improved support for presentation document type
* [loviewer] Added keyboard shortcuts.
             (PgUp, PgDown, Ctrl+Home, Ctrl+End, Up, Down, Left, Right, Ctrl+Plus, Ctrl+Minus)
             See src/app/qml/loView/KeybHelper.js
* [loviewer] Added an image provider for slides thumbnails, sync'd with RenderEngine
* [loviewer] Conditional layout for the presentation view: use a bottom edge or a sidebar to show the list of slides
* [loviewer] Moved zoom controls into a separate page head
* Updated translation template
-- 
Your team Ubuntu Document Viewer Developers is requested to review the proposed merge of lp:~verzegnassi-stefano/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-26 13:46:19 +0000
+++ po/com.ubuntu.docviewer.pot	2015-09-26 13:46:19 +0000
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-09-22 21:00+0200\n"
+"POT-Creation-Date: 2015-09-23 18:40+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"
@@ -254,7 +254,7 @@
 
 #: ../src/app/qml/documentPage/DocumentPageSearchHeader.qml:27
 #: ../src/app/qml/loView/LOViewDefaultHeader.qml:79
-#: ../src/app/qml/loView/LOViewPage.qml:186
+#: ../src/app/qml/loView/LOViewPage.qml:191
 #: ../src/app/qml/pdfView/PdfViewDefaultHeader.qml:61
 #: ../src/app/qml/textView/TextViewDefaultHeader.qml:61
 msgid "Back"
@@ -355,8 +355,8 @@
 msgid "Enable night mode"
 msgstr ""
 
-#: ../src/app/qml/loView/LOViewPage.qml:41
-#: ../src/app/qml/loView/LOViewPage.qml:184
+#: ../src/app/qml/loView/LOViewPage.qml:34
+#: ../src/app/qml/loView/LOViewPage.qml:189
 msgid "Slides"
 msgstr ""
 
@@ -372,6 +372,11 @@
 msgid "Zoom out"
 msgstr ""
 
+#: ../src/app/qml/loView/SlideControllerPanel.qml:52
+#, qt-format
+msgid "Slide %1 of %2"
+msgstr ""
+
 #: ../src/app/qml/loView/ZoomSelector.qml:29
 msgid "Automatic (Fit width)"
 msgstr ""

=== modified file 'src/app/qml/loView/KeybHelper.js'
--- src/app/qml/loView/KeybHelper.js	2015-09-26 13:46:19 +0000
+++ src/app/qml/loView/KeybHelper.js	2015-09-26 13:46:19 +0000
@@ -16,42 +16,129 @@
  
 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 (loDocument.documentType == LO.Document.PresentationDocument)
-            loDocument.currentPart -= 1
+        if (isPresentation)
+            view.document.currentPart -= 1
         else
-            loPage.moveView("vertical", -loView.height)
+            view.moveView("vertical", -view.height)
  
         return;
     }
  
     if (event.key == Qt.Key_PageDown) {
-        if (loDocument.documentType == LO.Document.PresentationDocument)
-            loDocument.currentPart += 1
+        if (isPresentation)
+            view.document.currentPart += 1
         else
-            loPage.moveView("vertical", loView.height)
+            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) {
-        loPage.moveView("vertical", -pixelDiff)
+        view.moveView("vertical", -pixelDiff)
         return;
     }
  
     if (event.key == Qt.Key_Down) {
-        loPage.moveView("vertical", pixelDiff)
+        view.moveView("vertical", pixelDiff)
         return;
     }
  
     if (event.key == Qt.Key_Left) {
-        loPage.moveView("horizontal", -pixelDiff)
+        view.moveView("horizontal", -pixelDiff)
         return;
     }
  
     if (event.key == Qt.Key_Right) {
-        loPage.moveView("horizontal", pixelDiff)
+        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/LOViewPage.qml'
--- src/app/qml/loView/LOViewPage.qml	2015-09-26 13:46:19 +0000
+++ src/app/qml/loView/LOViewPage.qml	2015-09-26 13:46:19 +0000
@@ -31,6 +31,7 @@
 
     readonly property bool wideWindow: width > units.gu(120)
 
+    bottomEdgeTitle: i18n.tr("Slides")
     bottomEdgeEnabled: {
         if (!loPageContentLoader.loaded)
             return false
@@ -38,7 +39,6 @@
         // else
         return loPageContentLoader.item.loDocument.documentType == LO.Document.PresentationDocument && !wideWindow
     }
-    bottomEdgeTitle: i18n.tr("Slides")
 
     Loader {
         id: loPageContentLoader
@@ -70,23 +70,14 @@
     Component {
         id: loPageContentComponent
 
-        Item {
+        FocusScope {
             id: loPageContent
             anchors.fill: parent
+
             property alias loDocument: loView.document
             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
@@ -108,8 +99,8 @@
                                     left: parent.left
                                 }
 
-                                model: partsModel
-                                visible: model
+                                model: LO.PartsModel { document: loPageContent.loDocument }
+                                visible: model && loDocument.documentType == LO.Document.PresentationDocument
                                 width: visible ? units.gu(40) : 0
                             }
 
@@ -120,7 +111,16 @@
                                     top: parent.top
                                     bottom: bottomBarLayoutItem.top
                                 }
-                                ItemLayout { item: "loView"; anchors.fill: parent }
+
+                                ItemLayout {
+                                    item: "loView"
+                                    anchors.fill: parent
+
+                                    // Keyboard events
+                                    focus: true
+                                    Keys.onPressed: KeybHelper.parseEvent(event)
+                                    Component.onCompleted: loPageContent.forceActiveFocus()
+                                }
                             }
 
                             Item {
@@ -153,10 +153,15 @@
                     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 }
@@ -191,6 +196,7 @@
                     flickable: null
 
                     PartsView {
+                        property bool belongsToNestedPage: true
                         anchors.fill: parent
                         model: LO.PartsModel { document: loPageContent.loDocument }
                     }

=== modified file 'src/app/qml/loView/PartsView.qml'
--- src/app/qml/loView/PartsView.qml	2015-09-26 13:46:19 +0000
+++ src/app/qml/loView/PartsView.qml	2015-09-26 13:46:19 +0000
@@ -29,6 +29,7 @@
     property bool expanded: true
 
     currentIndex: view.model ? view.model.document.currentPart : -1
+    highlightMoveDuration: UbuntuAnimation.SnapDuration
 
     delegate: ListItemWithActions {
         id: delegate
@@ -45,7 +46,12 @@
 
             onClicked: {
                 view.model.document.currentPart = model.index
-                pageStack.pop();
+
+                // 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();
             }
         }
 
@@ -57,6 +63,9 @@
                 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: false
 
                 source: "image://lok/part/" + model.index
             }

=== modified file 'src/app/qml/loView/SlideControllerPanel.qml'
--- src/app/qml/loView/SlideControllerPanel.qml	2015-09-26 13:46:19 +0000
+++ src/app/qml/loView/SlideControllerPanel.qml	2015-09-26 13:46:19 +0000
@@ -34,14 +34,24 @@
 
     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
+        //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"
@@ -49,13 +59,12 @@
         }
 
         Label {
-            text: "%1 of %2".arg(loPageContentLoader.item.loDocument.currentPart + 1)
-                            .arg(loPageContentLoader.item.loDocument.partsCount)
+            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.loDocument.currentPart += 1
+            onClicked: loPageContentLoader.item.loView.goNextPart()
 
             Icon {
                 anchors.centerIn: parent
@@ -63,5 +72,16 @@
                 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"
+            }
+        }
     }
 }

=== modified file 'src/plugin/libreofficetoolkit-qml-plugin/lodocument.cpp'
--- src/plugin/libreofficetoolkit-qml-plugin/lodocument.cpp	2015-09-26 13:46:19 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/lodocument.cpp	2015-09-26 13:46:19 +0000
@@ -36,6 +36,7 @@
 
 LODocument::LODocument()
   : m_path("")
+  , m_currentPart(-1)
   , m_document(nullptr)
 {
     // This space is intentionally empty.
@@ -61,21 +62,18 @@
 }
 
 int LODocument::currentPart() {
-    if (!m_document)
-        return int(-1);
- 
-    return m_document->getPart();
+    return m_currentPart;
 }
- 
+
 void LODocument::setCurrentPart(int index)
 {
     if (!m_document)
         return;
- 
-    if (this->currentPart() == index || index < 0 || index > partsCount() - 1)
+
+    if (m_currentPart == index || index < 0 || index > partsCount() - 1)
         return;
- 
-    m_document->setPart(index);
+
+    m_currentPart = index;
     Q_EMIT currentPartChanged();
 }
 
@@ -97,6 +95,8 @@
     m_docType = DocumentType(m_document->getDocumentType());
     Q_EMIT documentTypeChanged();
 
+    setCurrentPart(m_document->getPart());
+
     m_document->initializeForRendering();
     qDebug() << "Document loaded successfully !";
 
@@ -127,6 +127,12 @@
 // the rect 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
@@ -148,6 +154,52 @@
     return result.rgbSwapped();
 }
 
+QImage LODocument::paintThumbnail(int part, qreal size)
+{
+    if (!m_document)
+        return QImage();
+
+#ifdef DEBUG_TILE_BENCHMARK
+    QElapsedTimer renderTimer;
+    renderTimer.start();
+#endif
+
+    // This is used by LOPartsImageProvider to temporarily change the current part,
+    // in order to generate thumbnails.
+
+    // FIXME: Sometimes docviewer crashes at m_document->getPart() when a
+    // document is being loaded.
+    if (m_document->getPart() != part)
+        m_document->setPart(part);
+
+    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);
+
+    // Restore the active part used for tile rendering.
+    if (m_currentPart != part)
+        m_document->setPart(m_currentPart);
+
+#ifdef DEBUG_TILE_BENCHMARK
+    qDebug() << "Time to render the thumbnail:" << renderTimer.elapsed() << "ms";
+#endif
+
+    return result.rgbSwapped();
+}
+
 int LODocument::partsCount()
 {
     if (!m_document)
@@ -164,17 +216,6 @@
     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-26 13:46:19 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/lodocument.h	2015-09-26 13:46:19 +0000
@@ -60,12 +60,10 @@
     QSize documentSize() const;
 
     QImage paintTile(const QSize& canvasSize, const QRect& tileSize, const qreal& zoom = 1.0);
+    QImage paintThumbnail(int part, qreal size);
 
     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);
@@ -77,6 +75,7 @@
 
 private:
     QString m_path;
+    int m_currentPart;
     DocumentType m_docType;
 
     bool loadDocument(const QString &pathNAme);

=== modified file 'src/plugin/libreofficetoolkit-qml-plugin/lopartsimageprovider.cpp'
--- src/plugin/libreofficetoolkit-qml-plugin/lopartsimageprovider.cpp	2015-09-26 13:46:19 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/lopartsimageprovider.cpp	2015-09-26 13:46:19 +0000
@@ -16,62 +16,35 @@
 
 #include "lopartsimageprovider.h"
 #include "lodocument.h"
-#include "config.h"
-#include "twips.h"
-
-#include <QDebug>
+#include "renderengine.h"
 
 LOPartsImageProvider::LOPartsImageProvider(LODocument *document)
     : QQuickImageProvider(QQuickImageProvider::Image, QQuickImageProvider::ForceAsynchronousImageLoading)
-{
-    m_document = document;
-}
+    , 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")
+    if (requestedSize.isNull() || type != "part" ||
+            m_document->documentType() != LODocument::PresentationDocument)
         return QImage();
 
+    // Wait for any in-progress rendering to be completed
+    while (RenderEngine::instance()->activeTaskCount() != 0) { }
+
+    // Lock the render engine
+    RenderEngine::instance()->setEnabled(false);
+
+    // Render the part to 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);
+    QImage result = m_document->paintThumbnail(partNumber, 256.0);
+
+    // Unlock the render engine
+    RenderEngine::instance()->setEnabled(true);
 
     return result;
-
 }

=== modified file 'src/plugin/libreofficetoolkit-qml-plugin/qml/Viewer.qml'
--- src/plugin/libreofficetoolkit-qml-plugin/qml/Viewer.qml	2015-09-26 13:46:19 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/qml/Viewer.qml	2015-09-26 13:46:19 +0000
@@ -32,6 +32,37 @@
         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)
@@ -51,4 +82,14 @@
 
         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-26 13:46:19 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/renderengine.cpp	2015-09-26 13:46:19 +0000
@@ -6,10 +6,13 @@
 
 RenderEngine::RenderEngine():
     QObject(nullptr),
-    m_activeTaskCount(0)
+    m_activeTaskCount(0),
+    m_enabled(true)
 {
     int itc = QThread::idealThreadCount();
     m_idealThreadCount = itc == -1 ? DefaultIdealThreadCount : itc;
+
+    connect(this, SIGNAL(enabledChanged()), this, SLOT(doNextTask()));
 }
 
 void RenderEngine::enqueueTask(const QSharedPointer<LODocument>& doc, const QRect& area, const qreal &zoom, int id)
@@ -43,7 +46,7 @@
     qDebug() << " ---- doNextTask" << m_activeTaskCount << m_queue.count();
 #endif
 
-    if (m_activeTaskCount >= m_idealThreadCount || !m_queue.count())
+    if (m_activeTaskCount >= m_idealThreadCount || !m_queue.count() || !m_enabled)
         return;
 
     m_activeTaskCount++;

=== modified file 'src/plugin/libreofficetoolkit-qml-plugin/renderengine.h'
--- src/plugin/libreofficetoolkit-qml-plugin/renderengine.h	2015-09-26 13:46:19 +0000
+++ src/plugin/libreofficetoolkit-qml-plugin/renderengine.h	2015-09-26 13:46:19 +0000
@@ -6,6 +6,7 @@
 #include <QSharedPointer>
 #include <QHash>
 #include <QQueue>
+#include <QAtomicInt>
 
 #include "lodocument.h"
 
@@ -45,17 +46,33 @@
         return s_instance;
     }
 
+    int activeTaskCount() { return m_activeTaskCount; }
+
+    bool enabled() { return m_enabled.loadAcquire(); }
+    void setEnabled(bool enabled) {
+        if (m_enabled.loadAcquire() == enabled)
+            return;
+
+        m_enabled.storeRelease(enabled);
+        Q_EMIT enabledChanged();
+    }
+
 Q_SIGNALS:
     void renderFinished(int id, QImage img);
+    void enabledChanged();
 
 private:
     Q_INVOKABLE void internalRenderCallback(int id, QImage img);
+
+private slots:
     void doNextTask();
 
 private:
     QQueue<EngineTask> m_queue;
     int m_activeTaskCount;
     int m_idealThreadCount;
+
+    QAtomicInt m_enabled;
 };
 
 #endif // RENDERENGINE_H


Follow ups