Blob Blame History Raw
From 90cd5fdf14031c259ddc4f9ecf9ada7c37fcf421 Mon Sep 17 00:00:00 2001
From: Pranav Kant <pranavk@gnome.org>
Date: Thu, 11 Jun 2015 22:00:11 +0530
Subject: [PATCH 050/398] lokdocview: Restructure this GObject class

This is a big messy commit restructuring the whole class to follow most
common practices followed by standard GObject classes, so that it can
keep gobject-introspection happy; hence, allowing this widget to be used
from other languages.

(cherry picked from commit 3061e486f9f9313c15cba6782edfaee96fe4f83d)

Change-Id: I10c34dad402d1ec586958b2db21ff44412c36cea
---
 include/LibreOfficeKit/LibreOfficeKitGtk.h         |   17 +-
 .../qa/gtktiledviewer/gtktiledviewer.cxx           |    4 +-
 libreofficekit/source/gtk/lokdocview.cxx           | 1856 +++++++++++---------
 3 files changed, 1041 insertions(+), 836 deletions(-)

diff --git a/include/LibreOfficeKit/LibreOfficeKitGtk.h b/include/LibreOfficeKit/LibreOfficeKitGtk.h
index bdd2e9ab90e3..7048dbefc0a1 100644
--- a/include/LibreOfficeKit/LibreOfficeKitGtk.h
+++ b/include/LibreOfficeKit/LibreOfficeKitGtk.h
@@ -25,23 +25,19 @@ G_BEGIN_DECLS
 #define LOK_IS_DOC_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),  LOK_TYPE_DOC_VIEW))
 #define LOK_DOC_VIEW_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj),  LOK_TYPE_DOC_VIEW, LOKDocViewClass))
 
-
-typedef struct _LOKDocView       LOKDocView;
-typedef struct _LOKDocViewClass  LOKDocViewClass;
+typedef struct _LOKDocView        LOKDocView;
+typedef struct _LOKDocViewClass   LOKDocViewClass;
+typedef struct _LOKDocViewPrivate LOKDocViewPrivate;
 
 struct _LOKDocView
 {
     GtkDrawingArea aDrawingArea;
-    struct LOKDocView_Impl* m_pImpl;
+    LOKDocViewPrivate* priv;
 };
 
 struct _LOKDocViewClass
 {
     GtkDrawingAreaClass parent_class;
-    void (* edit_changed)  (LOKDocView* pView, gboolean was_edit);
-    void (* command_changed) (LOKDocView* pView, char* new_state);
-    void (* search_not_found) (LOKDocView* pView, char* new_state);
-    void (* part_changed) (LOKDocView* pView, int new_part);
 };
 
 GType                          lok_doc_view_get_type               (void) G_GNUC_CONST;
@@ -78,9 +74,8 @@ void                           lok_doc_view_post_command           (LOKDocView*
                                                                     const char* pArguments);
 
 /// Posts a keyboard event to LibreOfficeKit.
-void                           lok_doc_view_post_key               (GtkWidget* pWidget,
-                                                                    GdkEventKey* pEvent,
-                                                                    gpointer pData);
+void                           lok_doc_view_post_key               (LOKDocView* pDocView,
+                                                                    GdkEvent* pEvent);
 
 float                          lok_doc_view_pixel_to_twip          (LOKDocView* pDocView,
                                                                     float fInput);
diff --git a/libreofficekit/qa/gtktiledviewer/gtktiledviewer.cxx b/libreofficekit/qa/gtktiledviewer/gtktiledviewer.cxx
index 580d5f66f683..8b006797c226 100644
--- a/libreofficekit/qa/gtktiledviewer/gtktiledviewer.cxx
+++ b/libreofficekit/qa/gtktiledviewer/gtktiledviewer.cxx
@@ -145,12 +145,12 @@ static void getVisibleAreaTwips(GdkRectangle* pArea)
 
 
 /// Handles the key-press-event of the window.
-static gboolean signalKey(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer pData)
+static gboolean signalKey(GtkWidget* /*pWidget*/, GdkEvent* pEvent, gpointer/* pData*/)
 {
     LOKDocView* pLOKDocView = LOK_DOC_VIEW(pDocView);
     if (!gtk_widget_get_visible(pFindbar) && bool(lok_doc_view_get_edit(pLOKDocView)))
         {
-            lok_doc_view_post_key(pWidget, pEvent, pData);
+            lok_doc_view_post_key(pLOKDocView, pEvent);
             return TRUE;
         }
     return FALSE;
diff --git a/libreofficekit/source/gtk/lokdocview.cxx b/libreofficekit/source/gtk/lokdocview.cxx
index b00556620288..7031be9b6536 100644
--- a/libreofficekit/source/gtk/lokdocview.cxx
+++ b/libreofficekit/source/gtk/lokdocview.cxx
@@ -36,26 +36,30 @@
 // Number of handles around a graphic selection.
 #define GRAPHIC_HANDLE_COUNT 8
 
-/// Holds data used by LOKDocView only.
-struct LOKDocView_Impl
+struct _LOKDocViewPrivate
 {
-    LOKDocView* m_pDocView;
-    TileBuffer m_aTileBuffer;
-
-    float m_fZoom;
-
+    gchar* m_aLOPath;
+    gchar* m_aDocPath;
+    guint m_nLoadProgress;
+    gboolean m_bIsLoading;
+    gboolean m_bCanZoomIn;
+    gboolean m_bCanZoomOut;
     LibreOfficeKit* m_pOffice;
     LibreOfficeKitDocument* m_pDocument;
-    long m_nDocumentWidthTwips;
-    long m_nDocumentHeightTwips;
+
+    TileBuffer m_aTileBuffer;
+
+    gfloat m_fZoom;
+    glong m_nDocumentWidthTwips;
+    glong m_nDocumentHeightTwips;
     /// View or edit mode.
-    bool m_bEdit;
+    gboolean m_bEdit;
     /// Position and size of the visible cursor.
     GdkRectangle m_aVisibleCursor;
     /// Cursor overlay is visible or hidden (for blinking).
-    bool m_bCursorOverlayVisible;
+    gboolean m_bCursorOverlayVisible;
     /// Cursor is visible or hidden (e.g. for graphic selection).
-    bool m_bCursorVisible;
+    gboolean m_bCursorVisible;
     /// Time of the last button press.
     guint32 m_nLastButtonPressTime;
     /// Time of the last button release.
@@ -67,7 +71,7 @@ struct LOKDocView_Impl
     /// Position and size of the selection end.
     GdkRectangle m_aTextSelectionEnd;
     GdkRectangle m_aGraphicSelection;
-    bool m_bInDragGraphicSelection;
+    gboolean m_bInDragGraphicSelection;
 
     /// @name Start/middle/end handle.
     ///@{
@@ -76,19 +80,19 @@ struct LOKDocView_Impl
     /// Rectangle of the text selection start handle, to know if the user clicked on it or not
     GdkRectangle m_aHandleStartRect;
     /// If we are in the middle of a drag of the text selection end handle.
-    bool m_bInDragStartHandle;
+    gboolean m_bInDragStartHandle;
     /// Bitmap of the text selection middle handle.
     cairo_surface_t* m_pHandleMiddle;
     /// Rectangle of the text selection middle handle, to know if the user clicked on it or not
     GdkRectangle m_aHandleMiddleRect;
     /// If we are in the middle of a drag of the text selection middle handle.
-    bool m_bInDragMiddleHandle;
+    gboolean m_bInDragMiddleHandle;
     /// Bitmap of the text selection end handle.
     cairo_surface_t* m_pHandleEnd;
     /// Rectangle of the text selection end handle, to know if the user clicked on it or not
     GdkRectangle m_aHandleEndRect;
     /// If we are in the middle of a drag of the text selection end handle.
-    bool m_bInDragEndHandle;
+    gboolean m_bInDragEndHandle;
     ///@}
 
     /// @name Graphic handles.
@@ -98,103 +102,38 @@ struct LOKDocView_Impl
     /// Rectangle of a graphic selection handle, to know if the user clicked on it or not.
     GdkRectangle m_aGraphicHandleRects[8];
     /// If we are in the middle of a drag of a graphic selection handle.
-    bool m_bInDragGraphicHandles[8];
+    gboolean m_bInDragGraphicHandles[8];
     ///@}
-
-    /// Callback data, allocated in lok_doc_view_callback_worker(), released in lok_doc_view_callback().
-    struct CallbackData
-    {
-        int m_nType;
-        std::string m_aPayload;
-        LOKDocView* m_pDocView;
-
-        CallbackData(int nType, const std::string& rPayload, LOKDocView* pDocView);
-    };
-
-
-    LOKDocView_Impl(LOKDocView* pDocView);
-    ~LOKDocView_Impl();
-    /// Connected to the destroy signal of LOKDocView, deletes its LOKDocView_Impl.
-    static void destroy(LOKDocView* pDocView, gpointer pData);
-    /// Connected to the draw of the GtkDrawingArea
-    static gboolean renderDocument(GtkWidget *widget, cairo_t *cr, gpointer user_data);
-    /// Implementation of draw event handler, invoked by renderDocument().
-    gboolean renderDocumentImpl(cairo_t* cr);
-    /// Receives a key press or release event.
-    void signalKey(GdkEventKey* pEvent);
-    /*
-     * The user drags the handle, which is below the cursor, but wants to move the
-     * cursor accordingly.
-     *
-     * @param pHandle the rectangle of the handle
-     * @param pEvent the motion event
-     * @param pPoint the computed point (output parameter)
-     */
-    static void getDragPoint(GdkRectangle* pHandle, GdkEventButton* pEvent, GdkPoint* pPoint);
-    /// Receives a button press event.
-    static gboolean signalButton(GtkWidget* pEventBox, GdkEventButton* pEvent, LOKDocView* pDocView);
-    /// Implementation of button press event handler, invoked by signalButton().
-    gboolean signalButtonImpl(GdkEventButton* pEvent);
-    /// Receives a motion event.
-    static gboolean signalMotion(GtkWidget* pEventBox, GdkEventButton* pEvent, LOKDocView* pDocView);
-    /// Implementation of motion event handler, invoked by signalMotion().
-    gboolean signalMotionImpl(GdkEventButton* pEvent);
-    /// Receives an expose event.
-    static gboolean renderOverlay(GtkWidget* pWidget, cairo_t* cr, gpointer userdata);
-    /// Implementation of expose event handler (renders cursor and selection overlay), invoked by renderOverlay().
-    gboolean renderOverlayImpl(cairo_t *cr);
-    /// Is rRectangle empty?
-    static bool isEmptyRectangle(const GdkRectangle& rRectangle);
-    /*
-     * Renders pHandle below an rCursor rectangle on pCairo.
-     * @param rRectangle output parameter, the rectangle that contains the rendered handle.
-     */
-    void renderHandle(cairo_t* pCairo, const GdkRectangle& rCursor, cairo_surface_t* pHandle, GdkRectangle& rRectangle);
-    /// Renders pHandle around an rSelection rectangle on pCairo.
-    void renderGraphicHandle(cairo_t* pCairo, const GdkRectangle& rSelection, cairo_surface_t* pHandle);
-    /// Takes care of the blinking cursor.
-    static gboolean handleTimeout(gpointer pData);
-    /// Implementation of the timeout handler, invoked by handleTimeout().
-    gboolean handleTimeoutImpl();
-    /// Returns the GdkRectangle of a x,y,width,height string.
-    GdkRectangle payloadToRectangle(const char* pPayload);
-    /// Returns the GdkRectangles of a x1,y1,w1,h1;x2,y2,w2,h2;... string.
-    std::vector<GdkRectangle> payloadToRectangles(const char* pPayload);
-    /// Returns the string representation of a LibreOfficeKitCallbackType enumeration element.
-    static const char* callbackTypeToString(int nType);
-    /// Invoked on the main thread if callbackWorker() requests so.
-    static gboolean callback(gpointer pData);
-    /// Invoked on the main thread if globalCallbackWorker() requests so.
-    static gboolean globalCallback(gpointer pData);
-    /// Implementation of the callback handler, invoked by callback();
-    gboolean callbackImpl(CallbackData* pCallbackData);
-    /// Our LOK callback, runs on the LO thread.
-    static void callbackWorker(int nType, const char* pPayload, void* pData);
-    /// Implementation of the callback worder handler, invoked by callbackWorker().
-    void callbackWorkerImpl(int nType, const char* pPayload);
-    /// Our global LOK callback, runs on the LO thread.
-    static void globalCallbackWorker(int nType, const char* pPayload, void* pData);
-    /// Implementation of the global callback worder handler, invoked by globalCallbackWorker().
-    void globalCallbackWorkerImpl(int nType, const char* pPayload);
-    /// Command state (various buttons like bold are toggled or not) is changed.
-    void commandChanged(const std::string& rPayload);
-    /// Search did not find any matches.
-    void searchNotFound(const std::string& rPayload);
-    /// LOK decided to change parts, need to update UI.
-    void setPart(const std::string& rPayload);
-    /// Sets the tiles enclosed by rRectangle as invalid in m_aTileBuffer
-    void setTilesInvalid(const GdkRectangle& rRectangle);
 };
 
 enum
 {
+    LOAD_CHANGED,
+    LOAD_FAILED,
     EDIT_CHANGED,
     COMMAND_CHANGED,
     SEARCH_NOT_FOUND,
     PART_CHANGED,
+    HYPERLINK_CLICKED,
+
     LAST_SIGNAL
 };
 
+enum
+{
+    PROP_0,
+
+    PROP_LO_PATH,
+    PROP_DOC_PATH,
+    PROP_EDITABLE,
+    PROP_LOAD_PROGRESS,
+    PROP_ZOOM,
+    PROP_IS_LOADING,
+    PROP_DOC_WIDTH,
+    PROP_DOC_HEIGHT,
+    PROP_CAN_ZOOM_IN,
+    PROP_CAN_ZOOM_OUT
+};
 
 static guint doc_view_signals[LAST_SIGNAL] = { 0 };
 
@@ -203,15 +142,26 @@ SAL_DLLPUBLIC_EXPORT GType lok_doc_view_get_type();
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wunused-function"
 #endif
-G_DEFINE_TYPE(LOKDocView, lok_doc_view, GTK_TYPE_DRAWING_AREA)
+G_DEFINE_TYPE_WITH_PRIVATE (LOKDocView, lok_doc_view, GTK_TYPE_DRAWING_AREA)
 #ifdef __GNUC__
 #pragma GCC diagnostic pop
 #endif
 
-namespace {
 
-/// Sets rWidth and rHeight from a "width, height" string.
-void payloadToSize(const char* pPayload, long& rWidth, long& rHeight)
+struct CallbackData
+{
+    int m_nType;
+    std::string m_aPayload;
+    LOKDocView* m_pDocView;
+
+    CallbackData(int nType, const std::string& rPayload, LOKDocView* pDocView)
+        : m_nType(nType),
+          m_aPayload(rPayload),
+          m_pDocView(pDocView) {}
+};
+
+static void
+payloadToSize(const char* pPayload, long& rWidth, long& rHeight)
 {
     rWidth = rHeight = 0;
     gchar** ppCoordinates = g_strsplit(pPayload, ", ", 2);
@@ -226,177 +176,70 @@ void payloadToSize(const char* pPayload, long& rWidth, long& rHeight)
     g_strfreev(ppCoordinates);
 }
 
-}
-
-
-
-namespace {
-
-/// Implementation of the global callback handler, invoked by globalCallback();
-gboolean globalCallbackImpl(LOKDocView_Impl::CallbackData* pCallback)
+/// Returns the string representation of a LibreOfficeKitCallbackType enumeration element.
+static const char*
+callbackTypeToString (int nType)
 {
-    switch (pCallback->m_nType)
+    switch (nType)
     {
+    case LOK_CALLBACK_INVALIDATE_TILES:
+        return "LOK_CALLBACK_INVALIDATE_TILES";
+    case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
+        return "LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR";
+    case LOK_CALLBACK_TEXT_SELECTION:
+        return "LOK_CALLBACK_TEXT_SELECTION";
+    case LOK_CALLBACK_TEXT_SELECTION_START:
+        return "LOK_CALLBACK_TEXT_SELECTION_START";
+    case LOK_CALLBACK_TEXT_SELECTION_END:
+        return "LOK_CALLBACK_TEXT_SELECTION_END";
+    case LOK_CALLBACK_CURSOR_VISIBLE:
+        return "LOK_CALLBACK_CURSOR_VISIBLE";
+    case LOK_CALLBACK_GRAPHIC_SELECTION:
+        return "LOK_CALLBACK_GRAPHIC_SELECTION";
+    case LOK_CALLBACK_HYPERLINK_CLICKED:
+        return "LOK_CALLBACK_HYPERLINK_CLICKED";
+    case LOK_CALLBACK_STATE_CHANGED:
+        return "LOK_CALLBACK_STATE_CHANGED";
     case LOK_CALLBACK_STATUS_INDICATOR_START:
-    {
-    }
-    break;
+        return "LOK_CALLBACK_STATUS_INDICATOR_START";
     case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
-    {
-    }
-    break;
+        return "LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE";
     case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
-    {
-    }
-    break;
-    default:
-        g_assert(false);
-        break;
+        return "LOK_CALLBACK_STATUS_INDICATOR_FINISH";
+    case LOK_CALLBACK_SEARCH_NOT_FOUND:
+        return "LOK_CALLBACK_SEARCH_NOT_FOUND";
+    case LOK_CALLBACK_DOCUMENT_SIZE_CHANGED:
+        return "LOK_CALLBACK_DOCUMENT_SIZE_CHANGED";
+    case LOK_CALLBACK_SET_PART:
+        return "LOK_CALLBACK_SET_PART";
     }
-    delete pCallback;
-
-    return G_SOURCE_REMOVE;
-}
-
-}
-
-LOKDocView_Impl::CallbackData::CallbackData(int nType, const std::string& rPayload, LOKDocView* pDocView)
-    : m_nType(nType),
-    m_aPayload(rPayload),
-    m_pDocView(pDocView)
-{
-}
-
-LOKDocView_Impl::LOKDocView_Impl(LOKDocView* pDocView)
-    : m_pDocView(pDocView),
-      m_aTileBuffer(TileBuffer(0,0)),
-      m_fZoom(1),
-      m_pOffice(0),
-      m_pDocument(0),
-      m_nDocumentWidthTwips(0),
-      m_nDocumentHeightTwips(0),
-      m_bEdit(false),
-      m_aVisibleCursor({0, 0, 0, 0}),
-      m_bCursorOverlayVisible(false),
-      m_bCursorVisible(true),
-      m_nLastButtonPressTime(0),
-      m_nLastButtonReleaseTime(0),
-      m_aTextSelectionStart({0, 0, 0, 0}),
-      m_aTextSelectionEnd({0, 0, 0, 0}),
-      m_aGraphicSelection({0, 0, 0, 0}),
-      m_bInDragGraphicSelection(false),
-
-      // Start/middle/end handle.
-      m_pHandleStart(0),
-      m_aHandleStartRect({0, 0, 0, 0}),
-      m_bInDragStartHandle(false),
-      m_pHandleMiddle(0),
-      m_aHandleMiddleRect({0, 0, 0, 0}),
-      m_bInDragMiddleHandle(false),
-      m_pHandleEnd(0),
-      m_aHandleEndRect({0, 0, 0, 0}),
-      m_bInDragEndHandle(false),
-
-      m_pGraphicHandle(0)
-{
-    memset(&m_aGraphicHandleRects, 0, sizeof(m_aGraphicHandleRects));
-    memset(&m_bInDragGraphicHandles, 0, sizeof(m_bInDragGraphicHandles));
-}
-
-LOKDocView_Impl::~LOKDocView_Impl()
-{
-    if (m_pDocument)
-        m_pDocument->pClass->destroy(m_pDocument);
-    if (m_pOffice)
-        m_pOffice->pClass->destroy(m_pOffice);
-    m_pDocument = 0;
-    m_pOffice = 0;
-}
-
-void LOKDocView_Impl::destroy(LOKDocView* pDocView, gpointer /*pData*/)
-{
-    // We specifically need to destroy the document when closing in order to ensure
-    // that lock files etc. are cleaned up.
-    delete pDocView->m_pImpl;
-}
-
-gboolean LOKDocView_Impl::renderDocument(GtkWidget* /*widget*/, cairo_t *cr, gpointer userdata)
-{
-    LOKDocView *pDocView = LOK_DOC_VIEW (userdata);
-    return pDocView->m_pImpl->renderDocumentImpl(cr);
+    return 0;
 }
 
-gboolean LOKDocView_Impl::renderDocumentImpl(cairo_t *pCairo)
+static bool
+isEmptyRectangle(const GdkRectangle& rRectangle)
 {
-    long nDocumentWidthPixels = twipToPixel(m_nDocumentWidthTwips, m_fZoom);
-    long nDocumentHeightPixels = twipToPixel(m_nDocumentHeightTwips, m_fZoom);
-    // Total number of rows / columns in this document.
-    guint nRows = ceil((double)nDocumentHeightPixels / nTileSizePixels);
-    guint nColumns = ceil((double)nDocumentWidthPixels / nTileSizePixels);
-    GdkRectangle aVisibleArea;
-
-    gdk_cairo_get_clip_rectangle (pCairo, &aVisibleArea);
-
-    aVisibleArea.x = pixelToTwip (aVisibleArea.x, m_fZoom);
-    aVisibleArea.y = pixelToTwip (aVisibleArea.y, m_fZoom);
-    aVisibleArea.width = pixelToTwip (aVisibleArea.width, m_fZoom);
-    aVisibleArea.height = pixelToTwip (aVisibleArea.height, m_fZoom);
-
-    // Render the tiles.
-    for (guint nRow = 0; nRow < nRows; ++nRow)
-    {
-        for (guint nColumn = 0; nColumn < nColumns; ++nColumn)
-        {
-            GdkRectangle aTileRectangleTwips, aTileRectanglePixels;
-            bool bPaint = true;
-
-            // Determine size of the tile: the rightmost/bottommost tiles may
-            // be smaller, and we need the size to decide if we need to repaint.
-            if (nColumn == nColumns - 1)
-                aTileRectanglePixels.width = nDocumentWidthPixels - nColumn * nTileSizePixels;
-            else
-                aTileRectanglePixels.width = nTileSizePixels;
-            if (nRow == nRows - 1)
-                aTileRectanglePixels.height = nDocumentHeightPixels - nRow * nTileSizePixels;
-            else
-                aTileRectanglePixels.height = nTileSizePixels;
-
-            // Determine size and position of the tile in document coordinates,
-            // so we can decide if we can skip painting for partial rendering.
-            aTileRectangleTwips.x = pixelToTwip(nTileSizePixels, m_fZoom) * nColumn;
-            aTileRectangleTwips.y = pixelToTwip(nTileSizePixels, m_fZoom) * nRow;
-            aTileRectangleTwips.width = pixelToTwip(aTileRectanglePixels.width, m_fZoom);
-            aTileRectangleTwips.height = pixelToTwip(aTileRectanglePixels.height, m_fZoom);
-
-            if (!gdk_rectangle_intersect(&aVisibleArea, &aTileRectangleTwips, 0))
-                bPaint = false;
-
-            if (bPaint)
-            {
-                Tile& currentTile = m_aTileBuffer.getTile(nRow, nColumn, m_fZoom);
-                GdkPixbuf* pPixBuf = currentTile.getBuffer();
-                gdk_cairo_set_source_pixbuf (pCairo, pPixBuf,
-                                             twipToPixel(aTileRectangleTwips.x, m_fZoom),
-                                             twipToPixel(aTileRectangleTwips.y, m_fZoom));
-                cairo_paint(pCairo);
-            }
-        }
-    }
-    return FALSE;
+    return rRectangle.x == 0 && rRectangle.y == 0 && rRectangle.width == 0 && rRectangle.height == 0;
 }
 
-void LOKDocView_Impl::signalKey(GdkEventKey* pEvent)
+static void
+signalKey (LOKDocView* pDocView, const GdkEvent* pEvent)
 {
+    LOKDocViewPrivate* priv = pDocView->priv;
     int nCharCode = 0;
     int nKeyCode = 0;
+    guint keyval;
+    GdkModifierType state;
+    gdk_event_get_keyval (pEvent, &keyval);
+    gdk_event_get_state (pEvent, &state);
 
-    if (!m_bEdit)
+    if (!priv->m_bEdit)
     {
         g_info("signalKey: not in edit mode, ignore");
         return;
     }
 
-    switch (pEvent->keyval)
+    switch (keyval)
     {
     case GDK_KEY_BackSpace:
         nKeyCode = com::sun::star::awt::Key::BACKSPACE;
@@ -423,325 +266,300 @@ void LOKDocView_Impl::signalKey(GdkEventKey* pEvent)
         nKeyCode = com::sun::star::awt::Key::RIGHT;
         break;
     default:
-        if (pEvent->keyval >= GDK_KEY_F1 && pEvent->keyval <= GDK_KEY_F26)
-            nKeyCode = com::sun::star::awt::Key::F1 + (pEvent->keyval - GDK_KEY_F1);
+        if (keyval >= GDK_KEY_F1 && keyval <= GDK_KEY_F26)
+            nKeyCode = com::sun::star::awt::Key::F1 + (keyval - GDK_KEY_F1);
         else
-            nCharCode = gdk_keyval_to_unicode(pEvent->keyval);
+            nCharCode = gdk_keyval_to_unicode(keyval);
     }
 
     // rsc is not public API, but should be good enough for debugging purposes.
     // If this is needed for real, then probably a new param of type
     // css::awt::KeyModifier is needed in postKeyEvent().
-    if (pEvent->state & GDK_SHIFT_MASK)
+    if (state & GDK_SHIFT_MASK)
         nKeyCode |= KEY_SHIFT;
 
     if (pEvent->type == GDK_KEY_RELEASE)
-        m_pDocument->pClass->postKeyEvent(m_pDocument, LOK_KEYEVENT_KEYUP, nCharCode, nKeyCode);
+        priv->m_pDocument->pClass->postKeyEvent(priv->m_pDocument, LOK_KEYEVENT_KEYUP, nCharCode, nKeyCode);
     else
-        m_pDocument->pClass->postKeyEvent(m_pDocument, LOK_KEYEVENT_KEYINPUT, nCharCode, nKeyCode);
-}
-
-gboolean LOKDocView_Impl::signalButton(GtkWidget* /*pEventBox*/, GdkEventButton* pEvent, LOKDocView* pDocView)
-{
-    return pDocView->m_pImpl->signalButtonImpl(pEvent);
+        priv->m_pDocument->pClass->postKeyEvent(priv->m_pDocument, LOK_KEYEVENT_KEYINPUT, nCharCode, nKeyCode);
 }
 
-/// Receives a button press event.
-gboolean LOKDocView_Impl::signalButtonImpl(GdkEventButton* pEvent)
+static gboolean
+handleTimeout (gpointer pData)
 {
-    g_info("LOKDocView_Impl::signalButton: %d, %d (in twips: %d, %d)", (int)pEvent->x, (int)pEvent->y, (int)pixelToTwip(pEvent->x, m_fZoom), (int)pixelToTwip(pEvent->y, m_fZoom));
+    LOKDocView* pDocView = LOK_DOC_VIEW (pData);
+    LOKDocViewPrivate* priv = pDocView->priv;
 
-    if (pEvent->type == GDK_BUTTON_RELEASE)
+    if (priv->m_bEdit)
     {
-        if (m_bInDragStartHandle)
-        {
-            g_info("LOKDocView_Impl::signalButton: end of drag start handle");
-            m_bInDragStartHandle = false;
-            return FALSE;
-        }
-        else if (m_bInDragMiddleHandle)
-        {
-            g_info("LOKDocView_Impl::signalButton: end of drag middle handle");
-            m_bInDragMiddleHandle = false;
-            return FALSE;
-        }
-        else if (m_bInDragEndHandle)
-        {
-            g_info("LOKDocView_Impl::signalButton: end of drag end handle");
-            m_bInDragEndHandle = false;
-            return FALSE;
-        }
+        if (priv->m_bCursorOverlayVisible)
+            priv->m_bCursorOverlayVisible = false;
+        else
+            priv->m_bCursorOverlayVisible = true;
+        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
+    }
 
-        for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
-        {
-            if (m_bInDragGraphicHandles[i])
-            {
-                g_info("LOKDocView_Impl::signalButton: end of drag graphic handle #%d", i);
-                m_bInDragGraphicHandles[i] = false;
-                m_pDocument->pClass->setGraphicSelection(m_pDocument, LOK_SETGRAPHICSELECTION_END, pixelToTwip(pEvent->x, m_fZoom), pixelToTwip(pEvent->y, m_fZoom));
-                return FALSE;
-            }
-        }
+    return G_SOURCE_CONTINUE;
+}
 
-        if (m_bInDragGraphicSelection)
-        {
-            g_info("LOKDocView_Impl::signalButton: end of drag graphic selection");
-            m_bInDragGraphicSelection = false;
-            m_pDocument->pClass->setGraphicSelection(m_pDocument, LOK_SETGRAPHICSELECTION_END, pixelToTwip(pEvent->x, m_fZoom), pixelToTwip(pEvent->y, m_fZoom));
-            return FALSE;
-        }
-    }
+static void
+commandChanged(LOKDocView* pDocView, const std::string& rString)
+{
+    g_signal_emit(pDocView, doc_view_signals[COMMAND_CHANGED], 0, rString.c_str());
+}
 
-    if (m_bEdit)
-    {
-        GdkRectangle aClick;
-        aClick.x = pEvent->x;
-        aClick.y = pEvent->y;
-        aClick.width = 1;
-        aClick.height = 1;
-        if (pEvent->type == GDK_BUTTON_PRESS)
-        {
-            if (gdk_rectangle_intersect(&aClick, &m_aHandleStartRect, NULL))
-            {
-                g_info("LOKDocView_Impl::signalButton: start of drag start handle");
-                m_bInDragStartHandle = true;
-                return FALSE;
-            }
-            else if (gdk_rectangle_intersect(&aClick, &m_aHandleMiddleRect, NULL))
-            {
-                g_info("LOKDocView_Impl::signalButton: start of drag middle handle");
-                m_bInDragMiddleHandle = true;
-                return FALSE;
-            }
-            else if (gdk_rectangle_intersect(&aClick, &m_aHandleEndRect, NULL))
-            {
-                g_info("LOKDocView_Impl::signalButton: start of drag end handle");
-                m_bInDragEndHandle = true;
-                return FALSE;
-            }
+static void
+searchNotFound(LOKDocView* pDocView, const std::string& rString)
+{
+    g_signal_emit(pDocView, doc_view_signals[SEARCH_NOT_FOUND], 0, rString.c_str());
+}
 
-            for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
-            {
-                if (gdk_rectangle_intersect(&aClick, &m_aGraphicHandleRects[i], NULL))
-                {
-                    g_info("LOKDocView_Impl::signalButton: start of drag graphic handle #%d", i);
-                    m_bInDragGraphicHandles[i] = true;
-                    m_pDocument->pClass->setGraphicSelection(m_pDocument,
-                                                             LOK_SETGRAPHICSELECTION_START,
-                                                             pixelToTwip(m_aGraphicHandleRects[i].x + m_aGraphicHandleRects[i].width / 2, m_fZoom),
-                                                             pixelToTwip(m_aGraphicHandleRects[i].y + m_aGraphicHandleRects[i].height / 2, m_fZoom));
-                    return FALSE;
-                }
-            }
-        }
-    }
+static void
+setPart(LOKDocView* pDocView, const std::string& rString)
+{
+    g_signal_emit(pDocView, doc_view_signals[PART_CHANGED], 0, std::stoi(rString));
+}
 
-    if (!m_bEdit)
-        lok_doc_view_set_edit(m_pDocView, TRUE);
+/// Implementation of the global callback handler, invoked by globalCallback();
+static gboolean
+globalCallback (gpointer pData)
+{
+    CallbackData* pCallback = static_cast<CallbackData*>(pData);
 
-    switch (pEvent->type)
+    switch (pCallback->m_nType)
     {
-    case GDK_BUTTON_PRESS:
+    case LOK_CALLBACK_STATUS_INDICATOR_START:
     {
-        int nCount = 1;
-        if ((pEvent->time - m_nLastButtonPressTime) < 250)
-            nCount++;
-        m_nLastButtonPressTime = pEvent->time;
-        m_pDocument->pClass->postMouseEvent(m_pDocument, LOK_MOUSEEVENT_MOUSEBUTTONDOWN, pixelToTwip(pEvent->x, m_fZoom), pixelToTwip(pEvent->y, m_fZoom), nCount);
-        break;
     }
-    case GDK_BUTTON_RELEASE:
+    break;
+    case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
     {
-        int nCount = 1;
-        if ((pEvent->time - m_nLastButtonReleaseTime) < 250)
-            nCount++;
-        m_nLastButtonReleaseTime = pEvent->time;
-        m_pDocument->pClass->postMouseEvent(m_pDocument, LOK_MOUSEEVENT_MOUSEBUTTONUP, pixelToTwip(pEvent->x, m_fZoom), pixelToTwip(pEvent->y, m_fZoom), nCount);
-        break;
     }
+    break;
+    case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
+    {
+    }
+    break;
     default:
+        g_assert(false);
         break;
     }
-    return FALSE;
+    delete pCallback;
+
+    return G_SOURCE_REMOVE;
 }
 
-void LOKDocView_Impl::getDragPoint(GdkRectangle* pHandle, GdkEventButton* pEvent, GdkPoint* pPoint)
+static void
+globalCallbackWorker(int nType, const char* pPayload, void* pData)
 {
-    GdkPoint aCursor, aHandle;
+    LOKDocView* pDocView = LOK_DOC_VIEW (pData);
 
-    // Center of the cursor rectangle: we know that it's above the handle.
-    aCursor.x = pHandle->x + pHandle->width / 2;
-    aCursor.y = pHandle->y - pHandle->height / 2;
-    // Center of the handle rectangle.
-    aHandle.x = pHandle->x + pHandle->width / 2;
-    aHandle.y = pHandle->y + pHandle->height / 2;
-    // Our target is the original cursor position + the dragged offset.
-    pPoint->x = aCursor.x + (pEvent->x - aHandle.x);
-    pPoint->y = aCursor.y + (pEvent->y - aHandle.y);
+    CallbackData* pCallback = new CallbackData(nType, pPayload ? pPayload : "(nil)", pDocView);
+    g_info("LOKDocView_Impl::globalCallbackWorkerImpl: %s, '%s'", callbackTypeToString(nType), pPayload);
+    gdk_threads_add_idle(globalCallback, pCallback);
 }
 
-gboolean LOKDocView_Impl::signalMotion(GtkWidget* /*pEventBox*/, GdkEventButton* pEvent, LOKDocView* pDocView)
+static GdkRectangle
+payloadToRectangle (LOKDocView* pDocView, const char* pPayload)
 {
-    return pDocView->m_pImpl->signalMotionImpl(pEvent);
-}
+    LOKDocViewPrivate* priv = pDocView->priv;
+    GdkRectangle aRet;
+    gchar** ppCoordinates = g_strsplit(pPayload, ", ", 4);
+    gchar** ppCoordinate = ppCoordinates;
 
-gboolean LOKDocView_Impl::signalMotionImpl(GdkEventButton* pEvent)
-{
-    GdkPoint aPoint;
+    aRet.width = aRet.height = aRet.x = aRet.y = 0;
 
-    if (m_bInDragMiddleHandle)
-    {
-        g_info("lcl_signalMotion: dragging the middle handle");
-        LOKDocView_Impl::getDragPoint(&m_aHandleMiddleRect, pEvent, &aPoint);
-        m_pDocument->pClass->setTextSelection(m_pDocument, LOK_SETTEXTSELECTION_RESET, pixelToTwip(aPoint.x, m_fZoom), pixelToTwip(aPoint.y, m_fZoom));
-        return FALSE;
-    }
-    if (m_bInDragStartHandle)
-    {
-        g_info("lcl_signalMotion: dragging the start handle");
-        LOKDocView_Impl::getDragPoint(&m_aHandleStartRect, pEvent, &aPoint);
-        m_pDocument->pClass->setTextSelection(m_pDocument, LOK_SETTEXTSELECTION_START, pixelToTwip(aPoint.x, m_fZoom), pixelToTwip(aPoint.y, m_fZoom));
-        return FALSE;
-    }
-    if (m_bInDragEndHandle)
-    {
-        g_info("lcl_signalMotion: dragging the end handle");
-        LOKDocView_Impl::getDragPoint(&m_aHandleEndRect, pEvent, &aPoint);
-        m_pDocument->pClass->setTextSelection(m_pDocument, LOK_SETTEXTSELECTION_END, pixelToTwip(aPoint.x, m_fZoom), pixelToTwip(aPoint.y, m_fZoom));
-        return FALSE;
-    }
-    for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
-    {
-        if (m_bInDragGraphicHandles[i])
-        {
-            g_info("lcl_signalMotion: dragging the graphic handle #%d", i);
-            return FALSE;
-        }
-    }
-    if (m_bInDragGraphicSelection)
-    {
-        g_info("lcl_signalMotion: dragging the graphic selection");
-        return FALSE;
-    }
+    if (!*ppCoordinate)
+        return aRet;
+    aRet.x = atoi(*ppCoordinate);
+    if (aRet.x < 0)
+        aRet.x = 0;
+    ++ppCoordinate;
+    if (!*ppCoordinate)
+        return aRet;
+    aRet.y = atoi(*ppCoordinate);
+    if (aRet.y < 0)
+        aRet.y = 0;
+    ++ppCoordinate;
+    if (!*ppCoordinate)
+        return aRet;
+    aRet.width = atoi(*ppCoordinate);
+    if (aRet.x + aRet.width > priv->m_nDocumentWidthTwips)
+        aRet.width = priv->m_nDocumentWidthTwips - aRet.x;
+    ++ppCoordinate;
+    if (!*ppCoordinate)
+        return aRet;
+    aRet.height = atoi(*ppCoordinate);
+    if (aRet.y + aRet.height > priv->m_nDocumentHeightTwips)
+        aRet.height = priv->m_nDocumentHeightTwips - aRet.y;
+    g_strfreev(ppCoordinates);
 
-    GdkRectangle aMotionInTwipsInTwips;
-    aMotionInTwipsInTwips.x = pixelToTwip(pEvent->x, m_fZoom);
-    aMotionInTwipsInTwips.y = pixelToTwip(pEvent->y, m_fZoom);
-    aMotionInTwipsInTwips.width = 1;
-    aMotionInTwipsInTwips.height = 1;
-    if (gdk_rectangle_intersect(&aMotionInTwipsInTwips, &m_aGraphicSelection, 0))
-    {
-        g_info("lcl_signalMotion: start of drag graphic selection");
-        m_bInDragGraphicSelection = true;
-        m_pDocument->pClass->setGraphicSelection(m_pDocument, LOK_SETGRAPHICSELECTION_START, pixelToTwip(pEvent->x, m_fZoom), pixelToTwip(pEvent->y, m_fZoom));
-        return FALSE;
-    }
+    return aRet;
+}
 
-    // Otherwise a mouse move, as on the desktop.
-    m_pDocument->pClass->postMouseEvent(m_pDocument, LOK_MOUSEEVENT_MOUSEMOVE, pixelToTwip(pEvent->x, m_fZoom), pixelToTwip(pEvent->y, m_fZoom), 1);
+static const std::vector<GdkRectangle>
+payloadToRectangles(LOKDocView* pDocView, const char* pPayload)
+{
+    std::vector<GdkRectangle> aRet;
 
-    return FALSE;
+    gchar** ppRectangles = g_strsplit(pPayload, "; ", 0);
+    for (gchar** ppRectangle = ppRectangles; *ppRectangle; ++ppRectangle)
+        aRet.push_back(payloadToRectangle(pDocView, *ppRectangle));
+    g_strfreev(ppRectangles);
+
+    return aRet;
 }
 
-gboolean LOKDocView_Impl::renderOverlay(GtkWidget* /*widget*/, cairo_t *cr, gpointer userdata)
+
+static void
+setTilesInvalid (LOKDocView* pDocView, const GdkRectangle& rRectangle)
 {
-    LOKDocView *pDocView = LOK_DOC_VIEW (userdata);
-    return pDocView->m_pImpl->renderOverlayImpl(cr);
+    LOKDocViewPrivate* priv = pDocView->priv;
+    GdkRectangle aRectanglePixels;
+    GdkPoint aStart, aEnd;
+
+    aRectanglePixels.x = twipToPixel(rRectangle.x, priv->m_fZoom);
+    aRectanglePixels.y = twipToPixel(rRectangle.y, priv->m_fZoom);
+    aRectanglePixels.width = twipToPixel(rRectangle.width, priv->m_fZoom);
+    aRectanglePixels.height = twipToPixel(rRectangle.height, priv->m_fZoom);
+
+    aStart.x = aRectanglePixels.y / nTileSizePixels;
+    aStart.y = aRectanglePixels.x / nTileSizePixels;
+    aEnd.x = (aRectanglePixels.y + aRectanglePixels.height + nTileSizePixels) / nTileSizePixels;
+    aEnd.y = (aRectanglePixels.x + aRectanglePixels.width + nTileSizePixels) / nTileSizePixels;
+
+    for (int i = aStart.x; i < aEnd.x; i++)
+        for (int j = aStart.y; j < aEnd.y; j++)
+            priv->m_aTileBuffer.setInvalid(i, j);
 }
 
-gboolean LOKDocView_Impl::renderOverlayImpl(cairo_t *pCairo)
+static gboolean
+callback (gpointer pData)
 {
-    if (m_bEdit && m_bCursorVisible && m_bCursorOverlayVisible && !isEmptyRectangle(m_aVisibleCursor))
-    {
-        if (m_aVisibleCursor.width < 30)
-            // Set a minimal width if it would be 0.
-            m_aVisibleCursor.width = 30;
+    CallbackData* pCallback = static_cast<CallbackData*>(pData);
+    LOKDocView* pDocView = LOK_DOC_VIEW (pCallback->m_pDocView);
+    LOKDocViewPrivate* priv = pDocView->priv;
 
-        cairo_set_source_rgb(pCairo, 0, 0, 0);
-        cairo_rectangle(pCairo,
-                        twipToPixel(m_aVisibleCursor.x, m_fZoom),
-                        twipToPixel(m_aVisibleCursor.y, m_fZoom),
-                        twipToPixel(m_aVisibleCursor.width, m_fZoom),
-                        twipToPixel(m_aVisibleCursor.height, m_fZoom));
-        cairo_fill(pCairo);
-    }
-
-    if (m_bEdit && m_bCursorVisible && !isEmptyRectangle(m_aVisibleCursor) && m_aTextSelectionRectangles.empty())
+    switch (pCallback->m_nType)
     {
-        // Have a cursor, but no selection: we need the middle handle.
-        if (!m_pHandleMiddle)
-            m_pHandleMiddle = cairo_image_surface_create_from_png(CURSOR_HANDLE_DIR "handle_middle.png");
-        renderHandle(pCairo, m_aVisibleCursor, m_pHandleMiddle, m_aHandleMiddleRect);
-    }
-
-    if (!m_aTextSelectionRectangles.empty())
+    case LOK_CALLBACK_INVALIDATE_TILES:
     {
-        for (GdkRectangle& rRectangle : m_aTextSelectionRectangles)
+        if (pCallback->m_aPayload != "EMPTY")
         {
-            // Blue with 75% transparency.
-            cairo_set_source_rgba(pCairo, ((double)0x43)/255, ((double)0xac)/255, ((double)0xe8)/255, 0.25);
-            cairo_rectangle(pCairo,
-                            twipToPixel(rRectangle.x, m_fZoom),
-                            twipToPixel(rRectangle.y, m_fZoom),
-                            twipToPixel(rRectangle.width, m_fZoom),
-                            twipToPixel(rRectangle.height, m_fZoom));
-            cairo_fill(pCairo);
+            GdkRectangle aRectangle = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
+            setTilesInvalid(pDocView, aRectangle);
         }
+        else
+            priv->m_aTileBuffer.resetAllTiles();
 
-        // Handles
-        if (!isEmptyRectangle(m_aTextSelectionStart))
-        {
-            // Have a start position: we need a start handle.
-            if (!m_pHandleStart)
-                m_pHandleStart = cairo_image_surface_create_from_png(CURSOR_HANDLE_DIR "handle_start.png");
-            renderHandle(pCairo, m_aTextSelectionStart, m_pHandleStart, m_aHandleStartRect);
-        }
-        if (!isEmptyRectangle(m_aTextSelectionEnd))
+        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
+    }
+    break;
+    case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
+    {
+        priv->m_aVisibleCursor = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
+        priv->m_bCursorOverlayVisible = true;
+        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
+    }
+    break;
+    case LOK_CALLBACK_TEXT_SELECTION:
+    {
+        priv->m_aTextSelectionRectangles = payloadToRectangles(pDocView, pCallback->m_aPayload.c_str());
+        // In case the selection is empty, then we get no LOK_CALLBACK_TEXT_SELECTION_START/END events.
+        if (priv->m_aTextSelectionRectangles.empty())
         {
-            // Have a start position: we need an end handle.
-            if (!m_pHandleEnd)
-                m_pHandleEnd = cairo_image_surface_create_from_png(CURSOR_HANDLE_DIR "handle_end.png");
-            renderHandle(pCairo, m_aTextSelectionEnd, m_pHandleEnd, m_aHandleEndRect);
+            memset(&priv->m_aTextSelectionStart, 0, sizeof(priv->m_aTextSelectionStart));
+            memset(&priv->m_aHandleStartRect, 0, sizeof(priv->m_aHandleStartRect));
+            memset(&priv->m_aTextSelectionEnd, 0, sizeof(priv->m_aTextSelectionEnd));
+            memset(&priv->m_aHandleEndRect, 0, sizeof(priv->m_aHandleEndRect));
         }
+        else
+            memset(&priv->m_aHandleMiddleRect, 0, sizeof(priv->m_aHandleMiddleRect));
     }
-
-    if (!isEmptyRectangle(m_aGraphicSelection))
+    break;
+    case LOK_CALLBACK_TEXT_SELECTION_START:
     {
-        if (!m_pGraphicHandle)
-            m_pGraphicHandle = cairo_image_surface_create_from_png(CURSOR_HANDLE_DIR "handle_graphic.png");
-        renderGraphicHandle(pCairo, m_aGraphicSelection, m_pGraphicHandle);
+        priv->m_aTextSelectionStart = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
     }
+    break;
+    case LOK_CALLBACK_TEXT_SELECTION_END:
+    {
+        priv->m_aTextSelectionEnd = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
+    }
+    break;
+    case LOK_CALLBACK_CURSOR_VISIBLE:
+    {
+        priv->m_bCursorVisible = pCallback->m_aPayload == "true";
+    }
+    break;
+    case LOK_CALLBACK_GRAPHIC_SELECTION:
+    {
+        if (pCallback->m_aPayload != "EMPTY")
+            priv->m_aGraphicSelection = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
+        else
+            memset(&priv->m_aGraphicSelection, 0, sizeof(priv->m_aGraphicSelection));
+        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
+    }
+    break;
+    case LOK_CALLBACK_HYPERLINK_CLICKED:
+    {
+        GError* pError = NULL;
+        gtk_show_uri(NULL, pCallback->m_aPayload.c_str(), GDK_CURRENT_TIME, &pError);
+    }
+    break;
+    case LOK_CALLBACK_STATE_CHANGED:
+    {
+        commandChanged(pDocView, pCallback->m_aPayload);
+    }
+    break;
+    case LOK_CALLBACK_SEARCH_NOT_FOUND:
+    {
+        searchNotFound(pDocView, pCallback->m_aPayload);
+    }
+    break;
+    case LOK_CALLBACK_DOCUMENT_SIZE_CHANGED:
+    {
+        g_info ("%d %d", priv->m_nDocumentWidthTwips, priv->m_nDocumentHeightTwips);
+        g_info ("startin");
+        payloadToSize(pCallback->m_aPayload.c_str(), priv->m_nDocumentWidthTwips, priv->m_nDocumentHeightTwips);
+        g_info ("%d %d", priv->m_nDocumentWidthTwips, priv->m_nDocumentHeightTwips);
+        gtk_widget_set_size_request(GTK_WIDGET(pDocView),
+                                    twipToPixel(priv->m_nDocumentWidthTwips, priv->m_fZoom),
+                                    twipToPixel(priv->m_nDocumentHeightTwips, priv->m_fZoom));
+    }
+    break;
+    case LOK_CALLBACK_SET_PART:
+    {
+        setPart(pDocView, pCallback->m_aPayload);
+    }
+    break;
+    default:
+        g_assert(false);
+        break;
+    }
+    delete pCallback;
 
-    return FALSE;
-}
-
-bool LOKDocView_Impl::isEmptyRectangle(const GdkRectangle& rRectangle)
-{
-    return rRectangle.x == 0 && rRectangle.y == 0 && rRectangle.width == 0 && rRectangle.height == 0;
+    return G_SOURCE_REMOVE;
 }
 
-void LOKDocView_Impl::setTilesInvalid(const GdkRectangle& rRectangle)
+static void
+callbackWorker (int nType, const char* pPayload, void* pData)
 {
-    GdkRectangle aRectanglePixels;
-    GdkPoint aStart, aEnd;
-
-    aRectanglePixels.x = twipToPixel(rRectangle.x, m_fZoom);
-    aRectanglePixels.y = twipToPixel(rRectangle.y, m_fZoom);
-    aRectanglePixels.width = twipToPixel(rRectangle.width, m_fZoom);
-    aRectanglePixels.height = twipToPixel(rRectangle.height, m_fZoom);
-
-    aStart.x = aRectanglePixels.y / nTileSizePixels;
-    aStart.y = aRectanglePixels.x / nTileSizePixels;
-    aEnd.x = (aRectanglePixels.y + aRectanglePixels.height + nTileSizePixels) / nTileSizePixels;
-    aEnd.y = (aRectanglePixels.x + aRectanglePixels.width + nTileSizePixels) / nTileSizePixels;
+    LOKDocView* pDocView = LOK_DOC_VIEW (pData);
 
-    for (int i = aStart.x; i < aEnd.x; i++)
-        for (int j = aStart.y; j < aEnd.y; j++)
-            m_aTileBuffer.setInvalid(i, j);
+    CallbackData* pCallback = new CallbackData(nType, pPayload ? pPayload : "(nil)", pDocView);
+    g_info("lok_doc_view_callbackWorker: %s, '%s'", callbackTypeToString(nType), pPayload);
+    gdk_threads_add_idle(callback, pCallback);
 }
 
-void LOKDocView_Impl::renderHandle(cairo_t* pCairo, const GdkRectangle& rCursor, cairo_surface_t* pHandle, GdkRectangle& rRectangle)
+static void
+renderHandle(LOKDocView* pDocView,
+             cairo_t* pCairo,
+             const GdkRectangle& rCursor,
+             cairo_surface_t* pHandle,
+             GdkRectangle& rRectangle)
 {
+    LOKDocViewPrivate* priv = pDocView->priv;
     GdkPoint aCursorBottom;
     int nHandleWidth, nHandleHeight;
     double fHandleScale;
@@ -749,16 +567,17 @@ void LOKDocView_Impl::renderHandle(cairo_t* pCairo, const GdkRectangle& rCursor,
     nHandleWidth = cairo_image_surface_get_width(pHandle);
     nHandleHeight = cairo_image_surface_get_height(pHandle);
     // We want to scale down the handle, so that its height is the same as the cursor caret.
-    fHandleScale = twipToPixel(rCursor.height, m_fZoom) / nHandleHeight;
+    fHandleScale = twipToPixel(rCursor.height, priv->m_fZoom) / nHandleHeight;
     // We want the top center of the handle bitmap to be at the bottom center of the cursor rectangle.
-    aCursorBottom.x = twipToPixel(rCursor.x, m_fZoom) + twipToPixel(rCursor.width, m_fZoom) / 2 - (nHandleWidth * fHandleScale) / 2;
-    aCursorBottom.y = twipToPixel(rCursor.y, m_fZoom) + twipToPixel(rCursor.height, m_fZoom);
-    cairo_save(pCairo);
+    aCursorBottom.x = twipToPixel(rCursor.x, priv->m_fZoom) + twipToPixel(rCursor.width, priv->m_fZoom) / 2 - (nHandleWidth * fHandleScale) / 2;
+    aCursorBottom.y = twipToPixel(rCursor.y, priv->m_fZoom) + twipToPixel(rCursor.height, priv->m_fZoom);
+
+    cairo_save (pCairo);
     cairo_translate(pCairo, aCursorBottom.x, aCursorBottom.y);
     cairo_scale(pCairo, fHandleScale, fHandleScale);
     cairo_set_source_surface(pCairo, pHandle, 0, 0);
     cairo_paint(pCairo);
-    cairo_restore(pCairo);
+    cairo_restore (pCairo);
 
     rRectangle.x = aCursorBottom.x;
     rRectangle.y = aCursorBottom.y;
@@ -767,23 +586,27 @@ void LOKDocView_Impl::renderHandle(cairo_t* pCairo, const GdkRectangle& rCursor,
 }
 
 /// Renders pHandle around an rSelection rectangle on pCairo.
-void LOKDocView_Impl::renderGraphicHandle(cairo_t* pCairo, const GdkRectangle& rSelection, cairo_surface_t* pHandle)
+static void
+renderGraphicHandle(LOKDocView* pDocView,
+                    cairo_t* pCairo,
+                    const GdkRectangle& rSelection,
+                    cairo_surface_t* pHandle)
 {
+    LOKDocViewPrivate* priv = pDocView->priv;
     int nHandleWidth, nHandleHeight;
     GdkRectangle aSelection;
 
     nHandleWidth = cairo_image_surface_get_width(pHandle);
     nHandleHeight = cairo_image_surface_get_height(pHandle);
 
-    aSelection.x = twipToPixel(rSelection.x, m_fZoom);
-    aSelection.y = twipToPixel(rSelection.y, m_fZoom);
-    aSelection.width = twipToPixel(rSelection.width, m_fZoom);
-    aSelection.height = twipToPixel(rSelection.height, m_fZoom);
+    aSelection.x = twipToPixel(rSelection.x, priv->m_fZoom);
+    aSelection.y = twipToPixel(rSelection.y, priv->m_fZoom);
+    aSelection.width = twipToPixel(rSelection.width, priv->m_fZoom);
+    aSelection.height = twipToPixel(rSelection.height, priv->m_fZoom);
 
     for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
     {
         int x = aSelection.x, y = aSelection.y;
-        cairo_save(pCairo);
 
         switch (i)
         {
@@ -819,364 +642,722 @@ void LOKDocView_Impl::renderGraphicHandle(cairo_t* pCairo, const GdkRectangle& r
         x -= nHandleWidth / 2;
         y -= nHandleHeight / 2;
 
-        m_aGraphicHandleRects[i].x = x;
-        m_aGraphicHandleRects[i].y = y;
-        m_aGraphicHandleRects[i].width = nHandleWidth;
-        m_aGraphicHandleRects[i].height = nHandleHeight;
+        priv->m_aGraphicHandleRects[i].x = x;
+        priv->m_aGraphicHandleRects[i].y = y;
+        priv->m_aGraphicHandleRects[i].width = nHandleWidth;
+        priv->m_aGraphicHandleRects[i].height = nHandleHeight;
 
+        cairo_save (pCairo);
         cairo_translate(pCairo, x, y);
         cairo_set_source_surface(pCairo, pHandle, 0, 0);
         cairo_paint(pCairo);
-        cairo_restore(pCairo);
+        cairo_restore (pCairo);
     }
 }
 
-gboolean LOKDocView_Impl::handleTimeout(gpointer pData)
-{
-    LOKDocView* pDocView = static_cast<LOKDocView*>(pData);
-    return pDocView->m_pImpl->handleTimeoutImpl();
-}
 
-gboolean LOKDocView_Impl::handleTimeoutImpl()
+static gboolean
+renderDocument(LOKDocView* pDocView, cairo_t* pCairo)
 {
-    if (m_bEdit)
+    LOKDocViewPrivate *priv = pDocView->priv;
+    GdkRectangle aVisibleArea;
+    long nDocumentWidthPixels = twipToPixel(priv->m_nDocumentWidthTwips, priv->m_fZoom);
+    long nDocumentHeightPixels = twipToPixel(priv->m_nDocumentHeightTwips, priv->m_fZoom);
+    // Total number of rows / columns in this document.
+    guint nRows = ceil((double)nDocumentHeightPixels / nTileSizePixels);
+    guint nColumns = ceil((double)nDocumentWidthPixels / nTileSizePixels);
+
+    gdk_cairo_get_clip_rectangle (pCairo, &aVisibleArea);
+    aVisibleArea.x = pixelToTwip (aVisibleArea.x, priv->m_fZoom);
+    aVisibleArea.y = pixelToTwip (aVisibleArea.y, priv->m_fZoom);
+    aVisibleArea.width = pixelToTwip (aVisibleArea.width, priv->m_fZoom);
+    aVisibleArea.height = pixelToTwip (aVisibleArea.height, priv->m_fZoom);
+
+    // Render the tiles.
+    for (guint nRow = 0; nRow < nRows; ++nRow)
     {
-        if (m_bCursorOverlayVisible)
-            m_bCursorOverlayVisible = false;
-        else
-            m_bCursorOverlayVisible = true;
-        gtk_widget_queue_draw(GTK_WIDGET(m_pDocView));
-    }
+        for (guint nColumn = 0; nColumn < nColumns; ++nColumn)
+        {
+            GdkRectangle aTileRectangleTwips, aTileRectanglePixels;
+            bool bPaint = true;
 
-    return G_SOURCE_CONTINUE;
-}
+            // Determine size of the tile: the rightmost/bottommost tiles may
+            // be smaller, and we need the size to decide if we need to repaint.
+            if (nColumn == nColumns - 1)
+                aTileRectanglePixels.width = nDocumentWidthPixels - nColumn * nTileSizePixels;
+            else
+                aTileRectanglePixels.width = nTileSizePixels;
+            if (nRow == nRows - 1)
+                aTileRectanglePixels.height = nDocumentHeightPixels - nRow * nTileSizePixels;
+            else
+                aTileRectanglePixels.height = nTileSizePixels;
 
-GdkRectangle LOKDocView_Impl::payloadToRectangle(const char* pPayload)
-{
-    GdkRectangle aRet;
+            // Determine size and position of the tile in document coordinates,
+            // so we can decide if we can skip painting for partial rendering.
+            aTileRectangleTwips.x = pixelToTwip(nTileSizePixels, priv->m_fZoom) * nColumn;
+            aTileRectangleTwips.y = pixelToTwip(nTileSizePixels, priv->m_fZoom) * nRow;
+            aTileRectangleTwips.width = pixelToTwip(aTileRectanglePixels.width, priv->m_fZoom);
+            aTileRectangleTwips.height = pixelToTwip(aTileRectanglePixels.height, priv->m_fZoom);
 
-    aRet.width = aRet.height = aRet.x = aRet.y = 0;
-    gchar** ppCoordinates = g_strsplit(pPayload, ", ", 4);
-    gchar** ppCoordinate = ppCoordinates;
-    if (!*ppCoordinate)
-        return aRet;
-    aRet.x = atoi(*ppCoordinate);
-    if (aRet.x < 0)
-        aRet.x = 0;
-    ++ppCoordinate;
-    if (!*ppCoordinate)
-        return aRet;
-    aRet.y = atoi(*ppCoordinate);
-    if (aRet.y < 0)
-        aRet.y = 0;
-    ++ppCoordinate;
-    if (!*ppCoordinate)
-        return aRet;
-    aRet.width = atoi(*ppCoordinate);
-    if (aRet.x + aRet.width > m_nDocumentWidthTwips)
-        aRet.width = m_nDocumentWidthTwips - aRet.x;
-    ++ppCoordinate;
-    if (!*ppCoordinate)
-        return aRet;
-    aRet.height = atoi(*ppCoordinate);
-    if (aRet.y + aRet.height > m_nDocumentHeightTwips)
-        aRet.height = m_nDocumentHeightTwips - aRet.y;
-    g_strfreev(ppCoordinates);
-    return aRet;
+            if (!gdk_rectangle_intersect(&aVisibleArea, &aTileRectangleTwips, 0))
+                bPaint = false;
+
+            if (bPaint)
+            {
+                Tile& currentTile = priv->m_aTileBuffer.getTile(nRow, nColumn, priv->m_fZoom);
+                GdkPixbuf* pPixBuf = currentTile.getBuffer();
+                gdk_cairo_set_source_pixbuf (pCairo, pPixBuf,
+                                             twipToPixel(aTileRectangleTwips.x, priv->m_fZoom),
+                                             twipToPixel(aTileRectangleTwips.y, priv->m_fZoom));
+                cairo_paint(pCairo);
+            }
+        }
+    }
+
+    return FALSE;
 }
 
-std::vector<GdkRectangle> LOKDocView_Impl::payloadToRectangles(const char* pPayload)
+static gboolean
+renderOverlay(LOKDocView* pDocView, cairo_t* pCairo)
 {
-    std::vector<GdkRectangle> aRet;
+    LOKDocViewPrivate *priv = pDocView->priv;
 
-    gchar** ppRectangles = g_strsplit(pPayload, "; ", 0);
-    for (gchar** ppRectangle = ppRectangles; *ppRectangle; ++ppRectangle)
-        aRet.push_back(payloadToRectangle(*ppRectangle));
-    g_strfreev(ppRectangles);
+    if (priv->m_bEdit && priv->m_bCursorVisible && priv->m_bCursorOverlayVisible && !isEmptyRectangle(priv->m_aVisibleCursor))
+    {
+        if (priv->m_aVisibleCursor.width < 30)
+            // Set a minimal width if it would be 0.
+            priv->m_aVisibleCursor.width = 30;
 
-    return aRet;
-}
+        cairo_set_source_rgb(pCairo, 0, 0, 0);
+        cairo_rectangle(pCairo,
+                        twipToPixel(priv->m_aVisibleCursor.x, priv->m_fZoom),
+                        twipToPixel(priv->m_aVisibleCursor.y, priv->m_fZoom),
+                        twipToPixel(priv->m_aVisibleCursor.width, priv->m_fZoom),
+                        twipToPixel(priv->m_aVisibleCursor.height, priv->m_fZoom));
+        cairo_fill(pCairo);
+    }
 
-/// Returns the string representation of a LibreOfficeKitCallbackType enumeration element.
-const char* LOKDocView_Impl::callbackTypeToString(int nType)
-{
-    switch (nType)
+    if (priv->m_bEdit && priv->m_bCursorVisible && !isEmptyRectangle(priv->m_aVisibleCursor) && priv->m_aTextSelectionRectangles.empty())
     {
-    case LOK_CALLBACK_INVALIDATE_TILES:
-        return "LOK_CALLBACK_INVALIDATE_TILES";
-    case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
-        return "LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR";
-    case LOK_CALLBACK_TEXT_SELECTION:
-        return "LOK_CALLBACK_TEXT_SELECTION";
-    case LOK_CALLBACK_TEXT_SELECTION_START:
-        return "LOK_CALLBACK_TEXT_SELECTION_START";
-    case LOK_CALLBACK_TEXT_SELECTION_END:
-        return "LOK_CALLBACK_TEXT_SELECTION_END";
-    case LOK_CALLBACK_CURSOR_VISIBLE:
-        return "LOK_CALLBACK_CURSOR_VISIBLE";
-    case LOK_CALLBACK_GRAPHIC_SELECTION:
-        return "LOK_CALLBACK_GRAPHIC_SELECTION";
-    case LOK_CALLBACK_HYPERLINK_CLICKED:
-        return "LOK_CALLBACK_HYPERLINK_CLICKED";
-    case LOK_CALLBACK_STATE_CHANGED:
-        return "LOK_CALLBACK_STATE_CHANGED";
-    case LOK_CALLBACK_STATUS_INDICATOR_START:
-        return "LOK_CALLBACK_STATUS_INDICATOR_START";
-    case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
-        return "LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE";
-    case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
-        return "LOK_CALLBACK_STATUS_INDICATOR_FINISH";
-    case LOK_CALLBACK_SEARCH_NOT_FOUND:
-        return "LOK_CALLBACK_SEARCH_NOT_FOUND";
-    case LOK_CALLBACK_DOCUMENT_SIZE_CHANGED:
-        return "LOK_CALLBACK_DOCUMENT_SIZE_CHANGED";
-    case LOK_CALLBACK_SET_PART:
-        return "LOK_CALLBACK_SET_PART";
+        // Have a cursor, but no selection: we need the middle handle.
+        gchar* handleMiddlePath = g_strconcat (priv->m_aLOPath, "/../..", CURSOR_HANDLE_DIR, "handle_middle.png", NULL);
+        if (!priv->m_pHandleMiddle)
+            priv->m_pHandleMiddle = cairo_image_surface_create_from_png(handleMiddlePath);
+        g_free (handleMiddlePath);
+        renderHandle(pDocView, pCairo, priv->m_aVisibleCursor, priv->m_pHandleMiddle, priv->m_aHandleMiddleRect);
+    }
+
+    if (!priv->m_aTextSelectionRectangles.empty())
+    {
+        for (GdkRectangle& rRectangle : priv->m_aTextSelectionRectangles)
+        {
+            // Blue with 75% transparency.
+            cairo_set_source_rgba(pCairo, ((double)0x43)/255, ((double)0xac)/255, ((double)0xe8)/255, 0.25);
+            cairo_rectangle(pCairo,
+                            twipToPixel(rRectangle.x, priv->m_fZoom),
+                            twipToPixel(rRectangle.y, priv->m_fZoom),
+                            twipToPixel(rRectangle.width, priv->m_fZoom),
+                            twipToPixel(rRectangle.height, priv->m_fZoom));
+            cairo_fill(pCairo);
+        }
+
+        // Handles
+        if (!isEmptyRectangle(priv->m_aTextSelectionStart))
+        {
+            // Have a start position: we need a start handle.
+            gchar* handleStartPath = g_strconcat (priv->m_aLOPath, "/../..", CURSOR_HANDLE_DIR, "handle_start.png", NULL);
+            if (!priv->m_pHandleStart)
+                priv->m_pHandleStart = cairo_image_surface_create_from_png(handleStartPath);
+            renderHandle(pDocView, pCairo, priv->m_aTextSelectionStart, priv->m_pHandleStart, priv->m_aHandleStartRect);
+            g_free (handleStartPath);
+        }
+        if (!isEmptyRectangle(priv->m_aTextSelectionEnd))
+        {
+            // Have a start position: we need an end handle.
+            gchar* handleEndPath = g_strconcat (priv->m_aLOPath, "/../..", CURSOR_HANDLE_DIR, "handle_end.png", NULL);
+            if (!priv->m_pHandleEnd)
+                priv->m_pHandleEnd = cairo_image_surface_create_from_png(handleEndPath);
+            renderHandle(pDocView, pCairo, priv->m_aTextSelectionEnd, priv->m_pHandleEnd, priv->m_aHandleEndRect);
+            g_free (handleEndPath);
+        }
     }
-    return 0;
-}
 
-gboolean LOKDocView_Impl::callback(gpointer pData)
-{
-    LOKDocView_Impl::CallbackData* pCallback = static_cast<LOKDocView_Impl::CallbackData*>(pData);
-    return pCallback->m_pDocView->m_pImpl->callbackImpl(pCallback);
-}
+    if (!isEmptyRectangle(priv->m_aGraphicSelection))
+    {
+        gchar* handleGraphicPath = g_strconcat (priv->m_aLOPath, "/../..", CURSOR_HANDLE_DIR, "handle_graphic.png", NULL);
+        if (!priv->m_pGraphicHandle)
+            priv->m_pGraphicHandle = cairo_image_surface_create_from_png(handleGraphicPath);
+        renderGraphicHandle(pDocView, pCairo, priv->m_aGraphicSelection, priv->m_pGraphicHandle);
+        g_free (handleGraphicPath);
+    }
 
-gboolean LOKDocView_Impl::globalCallback(gpointer pData)
-{
-    LOKDocView_Impl::CallbackData* pCallback = static_cast<LOKDocView_Impl::CallbackData*>(pData);
-    return globalCallbackImpl(pCallback);
+    return FALSE;
 }
 
-gboolean LOKDocView_Impl::callbackImpl(CallbackData* pCallback)
+static gboolean
+lok_doc_view_signal_button(GtkWidget* pWidget, GdkEventButton* pEvent)
 {
-    switch (pCallback->m_nType)
-    {
-    case LOK_CALLBACK_INVALIDATE_TILES:
+    LOKDocView* pDocView = LOK_DOC_VIEW (pWidget);
+    LOKDocViewPrivate *priv = pDocView->priv;
+
+    g_info("LOKDocView_Impl::signalButton: %d, %d (in twips: %d, %d)",
+           (int)pEvent->x, (int)pEvent->y,
+           (int)pixelToTwip(pEvent->x, priv->m_fZoom),
+           (int)pixelToTwip(pEvent->y, priv->m_fZoom));
+
+    if (pEvent->type == GDK_BUTTON_RELEASE)
     {
-        if (pCallback->m_aPayload != "EMPTY")
+        if (priv->m_bInDragStartHandle)
         {
-            GdkRectangle aRectangle = LOKDocView_Impl::payloadToRectangle(pCallback->m_aPayload.c_str());
-            setTilesInvalid(aRectangle);
+            g_info("LOKDocView_Impl::signalButton: end of drag start handle");
+            priv->m_bInDragStartHandle = false;
+            return FALSE;
+        }
+        else if (priv->m_bInDragMiddleHandle)
+        {
+            g_info("LOKDocView_Impl::signalButton: end of drag middle handle");
+            priv->m_bInDragMiddleHandle = false;
+            return FALSE;
+        }
+        else if (priv->m_bInDragEndHandle)
+        {
+            g_info("LOKDocView_Impl::signalButton: end of drag end handle");
+            priv->m_bInDragEndHandle = false;
+            return FALSE;
         }
-        else
-            m_aTileBuffer.resetAllTiles();
 
-        gtk_widget_queue_draw(GTK_WIDGET(m_pDocView));
-    }
-    break;
-    case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
-    {
-        m_aVisibleCursor = LOKDocView_Impl::payloadToRectangle(pCallback->m_aPayload.c_str());
-        m_bCursorOverlayVisible = true;
-        gtk_widget_queue_draw(GTK_WIDGET(m_pDocView));
-    }
-    break;
-    case LOK_CALLBACK_TEXT_SELECTION:
-    {
-        m_aTextSelectionRectangles = LOKDocView_Impl::payloadToRectangles(pCallback->m_aPayload.c_str());
+        for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
+        {
+            if (priv->m_bInDragGraphicHandles[i])
+            {
+                g_info("LOKDocView_Impl::signalButton: end of drag graphic handle #%d", i);
+                priv->m_bInDragGraphicHandles[i] = false;
+                priv->m_pDocument->pClass->setGraphicSelection(priv->m_pDocument, LOK_SETGRAPHICSELECTION_END, pixelToTwip(pEvent->x, priv->m_fZoom), pixelToTwip(pEvent->y, priv->m_fZoom));
+                return FALSE;
+            }
+        }
 
-        // In case the selection is empty, then we get no LOK_CALLBACK_TEXT_SELECTION_START/END events.
-        if (m_aTextSelectionRectangles.empty())
+        if (priv->m_bInDragGraphicSelection)
         {
-            memset(&m_aTextSelectionStart, 0, sizeof(m_aTextSelectionStart));
-            memset(&m_aHandleStartRect, 0, sizeof(m_aHandleStartRect));
-            memset(&m_aTextSelectionEnd, 0, sizeof(m_aTextSelectionEnd));
-            memset(&m_aHandleEndRect, 0, sizeof(m_aHandleEndRect));
+            g_info("LOKDocView_Impl::signalButton: end of drag graphic selection");
+            priv->m_bInDragGraphicSelection = false;
+            priv->m_pDocument->pClass->setGraphicSelection(priv->m_pDocument, LOK_SETGRAPHICSELECTION_END, pixelToTwip(pEvent->x, priv->m_fZoom), pixelToTwip(pEvent->y, priv->m_fZoom));
+            return FALSE;
         }
-        else
-            memset(&m_aHandleMiddleRect, 0, sizeof(m_aHandleMiddleRect));
     }
-    break;
-    case LOK_CALLBACK_TEXT_SELECTION_START:
+
+    if (priv->m_bEdit)
     {
-        m_aTextSelectionStart = LOKDocView_Impl::payloadToRectangle(pCallback->m_aPayload.c_str());
+        GdkRectangle aClick;
+        aClick.x = pEvent->x;
+        aClick.y = pEvent->y;
+        aClick.width = 1;
+        aClick.height = 1;
+        if (pEvent->type == GDK_BUTTON_PRESS)
+        {
+            if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleStartRect, NULL))
+            {
+                g_info("LOKDocView_Impl::signalButton: start of drag start handle");
+                priv->m_bInDragStartHandle = true;
+                return FALSE;
+            }
+            else if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleMiddleRect, NULL))
+            {
+                g_info("LOKDocView_Impl::signalButton: start of drag middle handle");
+                priv->m_bInDragMiddleHandle = true;
+                return FALSE;
+            }
+            else if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleEndRect, NULL))
+            {
+                g_info("LOKDocView_Impl::signalButton: start of drag end handle");
+                priv->m_bInDragEndHandle = true;
+                return FALSE;
+            }
+
+            for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
+            {
+                if (gdk_rectangle_intersect(&aClick, &priv->m_aGraphicHandleRects[i], NULL))
+                {
+                    g_info("LOKDocView_Impl::signalButton: start of drag graphic handle #%d", i);
+                    priv->m_bInDragGraphicHandles[i] = true;
+                    priv->m_pDocument->pClass->setGraphicSelection(priv->m_pDocument,
+                                                             LOK_SETGRAPHICSELECTION_START,
+                                                             pixelToTwip(priv->m_aGraphicHandleRects[i].x + priv->m_aGraphicHandleRects[i].width / 2, priv->m_fZoom),
+                                                             pixelToTwip(priv->m_aGraphicHandleRects[i].y + priv->m_aGraphicHandleRects[i].height / 2, priv->m_fZoom));
+                    return FALSE;
+                }
+            }
+        }
     }
-    break;
-    case LOK_CALLBACK_TEXT_SELECTION_END:
+
+    if (!priv->m_bEdit)
+        lok_doc_view_set_edit(pDocView, TRUE);
+
+    switch (pEvent->type)
     {
-        m_aTextSelectionEnd = LOKDocView_Impl::payloadToRectangle(pCallback->m_aPayload.c_str());
-    }
-    break;
-    case LOK_CALLBACK_CURSOR_VISIBLE:
+    case GDK_BUTTON_PRESS:
     {
-        m_bCursorVisible = pCallback->m_aPayload == "true";
+        int nCount = 1;
+        if ((pEvent->time - priv->m_nLastButtonPressTime) < 250)
+            nCount++;
+        priv->m_nLastButtonPressTime = pEvent->time;
+        priv->m_pDocument->pClass->postMouseEvent(priv->m_pDocument, LOK_MOUSEEVENT_MOUSEBUTTONDOWN, pixelToTwip(pEvent->x, priv->m_fZoom), pixelToTwip(pEvent->y, priv->m_fZoom), nCount);
+        break;
     }
-    break;
-    case LOK_CALLBACK_GRAPHIC_SELECTION:
+    case GDK_BUTTON_RELEASE:
     {
-        if (pCallback->m_aPayload != "EMPTY")
-            m_aGraphicSelection = LOKDocView_Impl::payloadToRectangle(pCallback->m_aPayload.c_str());
-        else
-            memset(&m_aGraphicSelection, 0, sizeof(m_aGraphicSelection));
-        gtk_widget_queue_draw(GTK_WIDGET(m_pDocView));
+        int nCount = 1;
+        if ((pEvent->time - priv->m_nLastButtonReleaseTime) < 250)
+            nCount++;
+        priv->m_nLastButtonReleaseTime = pEvent->time;
+        priv->m_pDocument->pClass->postMouseEvent(priv->m_pDocument, LOK_MOUSEEVENT_MOUSEBUTTONUP, pixelToTwip(pEvent->x, priv->m_fZoom), pixelToTwip(pEvent->y, priv->m_fZoom), nCount);
+        break;
     }
-    break;
-    case LOK_CALLBACK_HYPERLINK_CLICKED:
+    default:
+        break;
+    }
+    return FALSE;
+}
+
+static void
+getDragPoint(GdkRectangle* pHandle,
+             GdkEventMotion* pEvent,
+             GdkPoint* pPoint)
+{
+    GdkPoint aCursor, aHandle;
+
+    // Center of the cursor rectangle: we know that it's above the handle.
+    aCursor.x = pHandle->x + pHandle->width / 2;
+    aCursor.y = pHandle->y - pHandle->height / 2;
+    // Center of the handle rectangle.
+    aHandle.x = pHandle->x + pHandle->width / 2;
+    aHandle.y = pHandle->y + pHandle->height / 2;
+    // Our target is the original cursor position + the dragged offset.
+    pPoint->x = aCursor.x + (pEvent->x - aHandle.x);
+    pPoint->y = aCursor.y + (pEvent->y - aHandle.y);
+}
+
+static gboolean
+lok_doc_view_signal_motion (GtkWidget* pWidget, GdkEventMotion* pEvent)
+{
+    LOKDocView* pDocView = LOK_DOC_VIEW (pWidget);
+    LOKDocViewPrivate *priv = pDocView->priv;
+    GdkPoint aPoint;
+
+    if (priv->m_bInDragMiddleHandle)
     {
-        GError* pError = NULL;
-        gtk_show_uri(NULL, pCallback->m_aPayload.c_str(), GDK_CURRENT_TIME, &pError);
+        g_info("lcl_signalMotion: dragging the middle handle");
+        getDragPoint(&priv->m_aHandleMiddleRect, pEvent, &aPoint);
+        priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_RESET, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
+        return FALSE;
     }
-    break;
-    case LOK_CALLBACK_STATE_CHANGED:
+    if (priv->m_bInDragStartHandle)
     {
-        commandChanged(pCallback->m_aPayload);
+        g_info("lcl_signalMotion: dragging the start handle");
+        getDragPoint(&priv->m_aHandleStartRect, pEvent, &aPoint);
+        priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_START, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
+        return FALSE;
     }
-    break;
-    case LOK_CALLBACK_SEARCH_NOT_FOUND:
+    if (priv->m_bInDragEndHandle)
     {
-        searchNotFound(pCallback->m_aPayload);
+        g_info("lcl_signalMotion: dragging the end handle");
+        getDragPoint(&priv->m_aHandleEndRect, pEvent, &aPoint);
+        priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_END, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
+        return FALSE;
     }
-    break;
-    case LOK_CALLBACK_DOCUMENT_SIZE_CHANGED:
+    for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
     {
-        payloadToSize(pCallback->m_aPayload.c_str(), m_nDocumentWidthTwips, m_nDocumentHeightTwips);
-        gtk_widget_set_size_request(GTK_WIDGET(m_pDocView),
-                                    twipToPixel(m_nDocumentWidthTwips, m_fZoom),
-                                    twipToPixel(m_nDocumentHeightTwips, m_fZoom));
+        if (priv->m_bInDragGraphicHandles[i])
+        {
+            g_info("lcl_signalMotion: dragging the graphic handle #%d", i);
+            return FALSE;
+        }
     }
-    break;
-    case LOK_CALLBACK_SET_PART:
+    if (priv->m_bInDragGraphicSelection)
     {
-        setPart(pCallback->m_aPayload);
+        g_info("lcl_signalMotion: dragging the graphic selection");
+        return FALSE;
     }
-    break;
-    default:
-        g_assert(false);
-        break;
+
+    GdkRectangle aMotionInTwipsInTwips;
+    aMotionInTwipsInTwips.x = pixelToTwip(pEvent->x, priv->m_fZoom);
+    aMotionInTwipsInTwips.y = pixelToTwip(pEvent->y, priv->m_fZoom);
+    aMotionInTwipsInTwips.width = 1;
+    aMotionInTwipsInTwips.height = 1;
+    if (gdk_rectangle_intersect(&aMotionInTwipsInTwips, &priv->m_aGraphicSelection, 0))
+    {
+        g_info("lcl_signalMotion: start of drag graphic selection");
+        priv->m_bInDragGraphicSelection = true;
+        priv->m_pDocument->pClass->setGraphicSelection(priv->m_pDocument, LOK_SETGRAPHICSELECTION_START, pixelToTwip(pEvent->x, priv->m_fZoom), pixelToTwip(pEvent->y, priv->m_fZoom));
+        return FALSE;
     }
-    delete pCallback;
 
-    return G_SOURCE_REMOVE;
-}
+    // Otherwise a mouse move, as on the desktop.
+    priv->m_pDocument->pClass->postMouseEvent(priv->m_pDocument, LOK_MOUSEEVENT_MOUSEMOVE, pixelToTwip(pEvent->x, priv->m_fZoom), pixelToTwip(pEvent->y, priv->m_fZoom), 1);
 
-void LOKDocView_Impl::callbackWorker(int nType, const char* pPayload, void* pData)
-{
-    LOKDocView* pDocView = static_cast<LOKDocView*>(pData);
-    pDocView->m_pImpl->callbackWorkerImpl(nType, pPayload);
+    return FALSE;
 }
 
-void LOKDocView_Impl::globalCallbackWorker(int nType, const char* pPayload, void* pData)
+static void lok_doc_view_init (LOKDocView* pDocView)
 {
-    LOKDocView* pDocView = static_cast<LOKDocView*>(pData);
-    pDocView->m_pImpl->globalCallbackWorkerImpl(nType, pPayload);
+    pDocView->priv = static_cast<LOKDocViewPrivate*>(lok_doc_view_get_instance_private (pDocView));
+    pDocView->priv->m_bCursorVisible = true;
+
+    gtk_widget_add_events(GTK_WIDGET(pDocView),
+                          GDK_BUTTON_PRESS_MASK
+                          |GDK_BUTTON_RELEASE_MASK
+                          |GDK_BUTTON_MOTION_MASK
+                          |GDK_KEY_PRESS_MASK
+                          |GDK_KEY_RELEASE_MASK);
 }
 
-void LOKDocView_Impl::callbackWorkerImpl(int nType, const char* pPayload)
+static void lok_doc_view_set_property (GObject* object, guint propId, const GValue *value, GParamSpec *pspec)
 {
-    LOKDocView_Impl::CallbackData* pCallback = new LOKDocView_Impl::CallbackData(nType, pPayload ? pPayload : "(nil)", m_pDocView);
-    g_info("lok_doc_view_callback_worker: %s, '%s'", LOKDocView_Impl::callbackTypeToString(nType), pPayload);
-    gdk_threads_add_idle(LOKDocView_Impl::callback, pCallback);
+    LOKDocView* pDocView = LOK_DOC_VIEW (object);
+    LOKDocViewPrivate* priv = pDocView->priv;
+
+    switch (propId)
+    {
+    case PROP_LO_PATH:
+        priv->m_aLOPath = g_value_dup_string (value);
+        break;
+    case PROP_DOC_PATH:
+        priv->m_aDocPath = g_value_dup_string (value);
+        break;
+    case PROP_EDITABLE:
+        lok_doc_view_set_edit (pDocView, g_value_get_boolean (value));
+        break;
+    case PROP_ZOOM:
+        lok_doc_view_set_zoom (pDocView, g_value_get_float (value));
+        break;
+    case PROP_DOC_WIDTH:
+        priv->m_nDocumentWidthTwips = g_value_get_long (value);
+        break;
+    case PROP_DOC_HEIGHT:
+        priv->m_nDocumentHeightTwips = g_value_get_long (value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec);
+    }
 }
 
-void LOKDocView_Impl::globalCallbackWorkerImpl(int nType, const char* pPayload)
+static void lok_doc_view_get_property (GObject* object, guint propId, GValue *value, GParamSpec *pspec)
 {
-    LOKDocView_Impl::CallbackData* pCallback = new LOKDocView_Impl::CallbackData(nType, pPayload ? pPayload : "(nil)", m_pDocView);
-    g_info("LOKDocView_Impl::globalCallbackWorkerImpl: %s, '%s'", LOKDocView_Impl::callbackTypeToString(nType), pPayload);
-    gdk_threads_add_idle(LOKDocView_Impl::globalCallback, pCallback);
+    LOKDocView* pDocView = LOK_DOC_VIEW (object);
+    LOKDocViewPrivate* priv = pDocView->priv;
+
+    switch (propId)
+    {
+    case PROP_LO_PATH:
+        g_value_set_string (value, priv->m_aLOPath);
+        break;
+    case PROP_DOC_PATH:
+        g_value_set_string (value, priv->m_aDocPath);
+        break;
+    case PROP_EDITABLE:
+        g_value_set_boolean (value, priv->m_bEdit);
+        break;
+    case PROP_LOAD_PROGRESS:
+        g_value_set_uint (value, priv->m_nLoadProgress);
+        break;
+    case PROP_ZOOM:
+        g_value_set_float (value, priv->m_fZoom);
+        break;
+    case PROP_IS_LOADING:
+        g_value_set_boolean (value, priv->m_bIsLoading);
+        break;
+    case PROP_DOC_WIDTH:
+        g_value_set_long (value, priv->m_nDocumentWidthTwips);
+        break;
+    case PROP_DOC_HEIGHT:
+        g_value_set_long (value, priv->m_nDocumentHeightTwips);
+        break;
+    case PROP_CAN_ZOOM_IN:
+        g_value_set_boolean (value, priv->m_bCanZoomIn);
+        break;
+    case PROP_CAN_ZOOM_OUT:
+        g_value_set_boolean (value, priv->m_bCanZoomOut);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec);
+    }
 }
 
-void LOKDocView_Impl::commandChanged(const std::string& rString)
+static gboolean lok_doc_view_draw (GtkWidget* pWidget, cairo_t* pCairo)
 {
-    g_signal_emit(m_pDocView, doc_view_signals[COMMAND_CHANGED], 0, rString.c_str());
+    LOKDocView *pDocView = LOK_DOC_VIEW (pWidget);
+
+    renderDocument (pDocView, pCairo);
+    renderOverlay (pDocView, pCairo);
+
+    return FALSE;
 }
 
-void LOKDocView_Impl::searchNotFound(const std::string& rString)
+static void lok_doc_view_finalize (GObject* object)
 {
-    g_signal_emit(m_pDocView, doc_view_signals[SEARCH_NOT_FOUND], 0, rString.c_str());
+    LOKDocView* pDocView = LOK_DOC_VIEW (object);
+    LOKDocViewPrivate* priv = pDocView->priv;
+
+    if (priv->m_pDocument)
+        priv->m_pDocument->pClass->destroy (priv->m_pDocument);
+    if (priv->m_pOffice)
+        priv->m_pOffice->pClass->destroy (priv->m_pOffice);
+
+    G_OBJECT_CLASS (lok_doc_view_parent_class)->finalize (object);
 }
 
-void LOKDocView_Impl::setPart(const std::string& rString)
+static void lok_doc_view_constructed (GObject* object)
 {
-    g_signal_emit(m_pDocView, doc_view_signals[PART_CHANGED], 0, std::stoi(rString));
+    LOKDocView* pDocView = LOK_DOC_VIEW (object);
+    LOKDocViewPrivate* priv = pDocView->priv;
+
+    G_OBJECT_CLASS (lok_doc_view_parent_class)->constructed (object);
+
+    pDocView->priv->m_pOffice = lok_init (priv->m_aLOPath);
 }
 
 static void lok_doc_view_class_init (LOKDocViewClass* pClass)
 {
-    GObjectClass *gobject_class = G_OBJECT_CLASS(pClass);
-    pClass->edit_changed = NULL;
+    GObjectClass *pGObjectClass = G_OBJECT_CLASS(pClass);
+    GtkWidgetClass *pWidgetClass = GTK_WIDGET_CLASS(pClass);
+
+    pGObjectClass->get_property = lok_doc_view_get_property;
+    pGObjectClass->set_property = lok_doc_view_set_property;
+    pGObjectClass->finalize = lok_doc_view_finalize;
+    pGObjectClass->constructed = lok_doc_view_constructed;
+
+    pWidgetClass->draw = lok_doc_view_draw;
+    pWidgetClass->button_press_event = lok_doc_view_signal_button;
+    pWidgetClass->button_release_event = lok_doc_view_signal_button;
+    pWidgetClass->motion_notify_event = lok_doc_view_signal_motion;
+
+    /**
+     * LOKDocView:lopath:
+     *
+     * The absolute path of the LibreOffice install.
+     */
+    g_object_class_install_property (pGObjectClass,
+          PROP_LO_PATH,
+          g_param_spec_string("lopath",
+                              "LO Path",
+                              "LibreOffice Install Path",
+                              0,
+                              static_cast<GParamFlags>(G_PARAM_READWRITE
+                                                       | G_PARAM_CONSTRUCT_ONLY)));
+
+    /**
+     * LOKDocView:docpath:
+     *
+     * The path of the document that is currently being viewed.
+     */
+    g_object_class_install_property (pGObjectClass,
+          PROP_DOC_PATH,
+          g_param_spec_string("docpath",
+                              "Document Path",
+                              "The URI of the document to open",
+                              0,
+                              G_PARAM_READWRITE));
+
+    /**
+     * LOKDocView:editable:
+     *
+     * Whether the document loaded inside of #LOKDocView is editable or not.
+     */
+    g_object_class_install_property (pGObjectClass,
+          PROP_EDITABLE,
+          g_param_spec_boolean("editable",
+                               "Editable",
+                               "Whether the content is in edit mode or not",
+                               FALSE,
+                               G_PARAM_READWRITE));
+
+    /**
+     * LOKDocView:load-progress:
+     *
+     * The percent completion of the current loading operation of the
+     * document. This can be used for progress bars. Note that this is not a
+     * very accurate progress indicator, and its value might reset it couple of
+     * times to 0 and start again. You should not rely on its numbers.
+     */
+    g_object_class_install_property (pGObjectClass,
+          PROP_LOAD_PROGRESS,
+          g_param_spec_int("load-progress",
+                           "Estimated Load Progress",
+                           "Whether the content is in edit mode or not",
+                           0, 100, 0,
+                           G_PARAM_READABLE));
+
+    /**
+     * LOKDocView:zoom-level:
+     *
+     * The current zoom level of the document loaded inside #LOKDocView. The
+     * default value is 1.0.
+     */
+    g_object_class_install_property (pGObjectClass,
+          PROP_ZOOM,
+          g_param_spec_float("zoom-level",
+                             "Zoom Level",
+                             "The current zoom level of the content",
+                             0, 5.0, 1.0,
+                             static_cast<GParamFlags>(G_PARAM_READWRITE |
+                                                      G_PARAM_CONSTRUCT)));
+
+    /**
+     * LOKDocView:is-loading:
+     *
+     * Whether the requested document is being loaded or not. %TRUE if it is
+     * being loaded, otherwise %FALSE.
+     */
+    g_object_class_install_property (pGObjectClass,
+          PROP_IS_LOADING,
+          g_param_spec_boolean("is-loading",
+                               "Is Loading",
+                               "Whether the view is loading a document",
+                               FALSE,
+                               G_PARAM_READABLE));
+
+    /**
+     * LOKDocView:doc-width:
+     *
+     * The width of the currently loaded document in #LOKDocView in twips.
+     */
+    g_object_class_install_property (pGObjectClass,
+          PROP_DOC_WIDTH,
+          g_param_spec_long("doc-width",
+                            "Document Width",
+                            "Width of the document in twips",
+                            0, G_MAXLONG, 0,
+                            G_PARAM_READWRITE));
+
+    /**
+     * LOKDocView:doc-height:
+     *
+     * The height of the currently loaded document in #LOKDocView in twips.
+     */
+    g_object_class_install_property (pGObjectClass,
+          PROP_DOC_HEIGHT,
+          g_param_spec_long("doc-height",
+                            "Document Height",
+                            "Height of the document in twips",
+                            0, G_MAXLONG, 0,
+                            G_PARAM_READWRITE));
+
+    /**
+     * LOKDocView:can-zoom-in:
+     *
+     * It tells whether the view can further be zoomed in or not.
+     */
+    g_object_class_install_property (pGObjectClass,
+          PROP_CAN_ZOOM_IN,
+          g_param_spec_boolean("can-zoom-in",
+                               "Can Zoom In",
+                               "Whether the view can be zoomed in further",
+                               TRUE,
+                               static_cast<GParamFlags>(G_PARAM_READABLE
+                                                       | G_PARAM_STATIC_STRINGS)));
+
+    /**
+     * LOKDocView:can-zoom-out:
+     *
+     * It tells whether the view can further be zoomed out or not.
+     */
+    g_object_class_install_property (pGObjectClass,
+          PROP_CAN_ZOOM_OUT,
+          g_param_spec_boolean("can-zoom-out",
+                               "Can Zoom Out",
+                               "Whether the view can be zoomed out further",
+                               TRUE,
+                               static_cast<GParamFlags>(G_PARAM_READABLE
+                                                       | G_PARAM_STATIC_STRINGS)));
+
+    /**
+     * LOKDocView::edit-changed:
+     * @pDocView: the #LOKDocView on which the signal is emitted
+     * @bEdit: the new edit value of the view
+     */
     doc_view_signals[EDIT_CHANGED] =
         g_signal_new("edit-changed",
-                     G_TYPE_FROM_CLASS (gobject_class),
+                     G_TYPE_FROM_CLASS (pGObjectClass),
                      G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET (LOKDocViewClass, edit_changed),
+                     0,
                      NULL, NULL,
                      g_cclosure_marshal_VOID__BOOLEAN,
                      G_TYPE_NONE, 1,
                      G_TYPE_BOOLEAN);
-    pClass->command_changed = NULL;
+
+    /**
+     * LOKDocView::command-changed:
+     * @pDocView: the #LOKDocView on which the signal is emitted
+     * @aCommand: the command that was changed
+     */
     doc_view_signals[COMMAND_CHANGED] =
         g_signal_new("command-changed",
-                     G_TYPE_FROM_CLASS(gobject_class),
+                     G_TYPE_FROM_CLASS(pGObjectClass),
                      G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(LOKDocViewClass, command_changed),
+                     0,
                      NULL, NULL,
                      g_cclosure_marshal_VOID__STRING,
                      G_TYPE_NONE, 1,
                      G_TYPE_STRING);
-    pClass->search_not_found = 0;
+
+    /**
+     * LOKDocView::search-not-found:
+     * @pDocView: the #LOKDocView on which the signal is emitted
+     * @aCommand: the string for which the search was not found.
+     */
     doc_view_signals[SEARCH_NOT_FOUND] =
         g_signal_new("search-not-found",
-                     G_TYPE_FROM_CLASS(gobject_class),
+                     G_TYPE_FROM_CLASS(pGObjectClass),
                      G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(LOKDocViewClass, search_not_found),
+                     0,
                      NULL, NULL,
                      g_cclosure_marshal_VOID__STRING,
                      G_TYPE_NONE, 1,
                      G_TYPE_STRING);
-    pClass->part_changed = 0;
+
+    /**
+     * LOKDocView::part-changed:
+     * @pDocView: the #LOKDocView on which the signal is emitted
+     * @aCommand: the part number which the view changed to
+     */
     doc_view_signals[PART_CHANGED] =
         g_signal_new("part-changed",
-                     G_TYPE_FROM_CLASS(gobject_class),
+                     G_TYPE_FROM_CLASS(pGObjectClass),
                      G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(LOKDocViewClass, part_changed),
+                     0,
                      NULL, NULL,
                      g_cclosure_marshal_VOID__INT,
                      G_TYPE_NONE, 1,
                      G_TYPE_INT);
-}
 
-static void lok_doc_view_init (LOKDocView* pDocView)
-{
-    pDocView->m_pImpl = new LOKDocView_Impl(pDocView);
-
-    g_signal_connect(G_OBJECT(pDocView),
-                     "draw",
-                     G_CALLBACK(LOKDocView_Impl::renderDocument), pDocView);
-    g_signal_connect(G_OBJECT(pDocView),
-                     "draw",
-                     G_CALLBACK(LOKDocView_Impl::renderOverlay), pDocView);
-    gtk_widget_add_events(GTK_WIDGET(pDocView),
-                           GDK_BUTTON_PRESS_MASK
-                          |GDK_BUTTON_RELEASE_MASK
-                          |GDK_BUTTON_MOTION_MASK);
-
-    g_signal_connect(G_OBJECT(pDocView),
-                     "button-press-event",
-                     G_CALLBACK(LOKDocView_Impl::signalButton), pDocView);
-    g_signal_connect(G_OBJECT(pDocView),
-                     "button-release-event",
-                     G_CALLBACK(LOKDocView_Impl::signalButton), pDocView);
-    g_signal_connect(G_OBJECT(pDocView),
-                     "motion-notify-event",
-                     G_CALLBACK(LOKDocView_Impl::signalMotion), pDocView);
-
-    g_signal_connect(G_OBJECT(pDocView), "destroy", G_CALLBACK(LOKDocView_Impl::destroy), 0);
+    /**
+     * LOKDocView::hyperlinked-clicked:
+     * @pDocView: the #LOKDocView on which the signal is emitted
+     * @aHyperlink: the URI which the application should handle
+     */
+    doc_view_signals[PART_CHANGED] =
+        g_signal_new("hyperlinked-clicked",
+                     G_TYPE_FROM_CLASS(pGObjectClass),
+                     G_SIGNAL_RUN_FIRST,
+                     0,
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__STRING,
+                     G_TYPE_NONE, 1,
+                     G_TYPE_STRING);
 }
 
+
+
 /**
  * lok_doc_view_new:
  * @pPath: LibreOffice install path.
  *
  * Returns: The #LOKDocView widget instance.
  */
-SAL_DLLPUBLIC_EXPORT GtkWidget* lok_doc_view_new(const char* pPath)
+SAL_DLLPUBLIC_EXPORT GtkWidget*
+lok_doc_view_new (const char* pPath)
 {
-    LOKDocView* pDocView = LOK_DOC_VIEW(g_object_new(LOK_TYPE_DOC_VIEW, NULL));
-    pDocView->m_pImpl->m_pOffice = lok_init (pPath);
-    if (pDocView->m_pImpl->m_pOffice == NULL)
-        return NULL;
-    return GTK_WIDGET( pDocView );
+    return GTK_WIDGET (g_object_new(LOK_TYPE_DOC_VIEW, "lopath", pPath, NULL));
 }
 
 /**
@@ -1186,47 +1367,47 @@ SAL_DLLPUBLIC_EXPORT GtkWidget* lok_doc_view_new(const char* pPath)
  *
  * Returns: %TRUE if the document is loaded succesfully, %FALSE otherwise
  */
-SAL_DLLPUBLIC_EXPORT gboolean lok_doc_view_open_document( LOKDocView* pDocView, char* pPath )
+SAL_DLLPUBLIC_EXPORT gboolean
+lok_doc_view_open_document (LOKDocView* pDocView, char* pPath)
 {
-    if ( pDocView->m_pImpl->m_pDocument )
+    if ( pDocView->priv->m_pDocument )
     {
-        pDocView->m_pImpl->m_pDocument->pClass->destroy( pDocView->m_pImpl->m_pDocument );
-        pDocView->m_pImpl->m_pDocument = 0;
+        pDocView->priv->m_pDocument->pClass->destroy( pDocView->priv->m_pDocument );
+        pDocView->priv->m_pDocument = 0;
     }
 
-    pDocView->m_pImpl->m_pOffice->pClass->registerCallback(pDocView->m_pImpl->m_pOffice, &LOKDocView_Impl::globalCallbackWorker, pDocView);
-    pDocView->m_pImpl->m_pDocument = pDocView->m_pImpl->m_pOffice->pClass->documentLoad( pDocView->m_pImpl->m_pOffice,
+    pDocView->priv->m_pOffice->pClass->registerCallback(pDocView->priv->m_pOffice, globalCallbackWorker, pDocView);
+    pDocView->priv->m_pDocument = pDocView->priv->m_pOffice->pClass->documentLoad( pDocView->priv->m_pOffice,
                                                                    pPath );
-    if ( !pDocView->m_pImpl->m_pDocument )
+    if ( !pDocView->priv->m_pDocument )
     {
         // FIXME: should have a GError parameter and populate it.
-        char *pError = pDocView->m_pImpl->m_pOffice->pClass->getError( pDocView->m_pImpl->m_pOffice );
+        char *pError = pDocView->priv->m_pOffice->pClass->getError( pDocView->priv->m_pOffice );
         fprintf( stderr, "Error opening document '%s'\n", pError );
         return FALSE;
     }
     else
     {
-        pDocView->m_pImpl->m_pDocument->pClass->initializeForRendering(pDocView->m_pImpl->m_pDocument);
-        pDocView->m_pImpl->m_pDocument->pClass->registerCallback(pDocView->m_pImpl->m_pDocument, &LOKDocView_Impl::callbackWorker, pDocView);
-        pDocView->m_pImpl->m_pDocument->pClass->getDocumentSize(pDocView->m_pImpl->m_pDocument, &pDocView->m_pImpl->m_nDocumentWidthTwips, &pDocView->m_pImpl->m_nDocumentHeightTwips);
-        g_timeout_add(600, &LOKDocView_Impl::handleTimeout, pDocView);
-
-        float zoom = pDocView->m_pImpl->m_fZoom;
-        long nDocumentWidthTwips = pDocView->m_pImpl->m_nDocumentWidthTwips;
-        long nDocumentHeightTwips = pDocView->m_pImpl->m_nDocumentHeightTwips;
+        pDocView->priv->m_pDocument->pClass->initializeForRendering(pDocView->priv->m_pDocument);
+        pDocView->priv->m_pDocument->pClass->registerCallback(pDocView->priv->m_pDocument, callbackWorker, pDocView);
+        pDocView->priv->m_pDocument->pClass->getDocumentSize(pDocView->priv->m_pDocument, &pDocView->priv->m_nDocumentWidthTwips, &pDocView->priv->m_nDocumentHeightTwips);
+        g_timeout_add(600, handleTimeout, pDocView);
+
+        float zoom = pDocView->priv->m_fZoom;
+        long nDocumentWidthTwips = pDocView->priv->m_nDocumentWidthTwips;
+        long nDocumentHeightTwips = pDocView->priv->m_nDocumentHeightTwips;
         long nDocumentWidthPixels = twipToPixel(nDocumentWidthTwips, zoom);
         long nDocumentHeightPixels = twipToPixel(nDocumentHeightTwips, zoom);
         // Total number of columns in this document.
         guint nColumns = ceil((double)nDocumentWidthPixels / nTileSizePixels);
 
 
-        pDocView->m_pImpl->m_aTileBuffer = TileBuffer(pDocView->m_pImpl->m_pDocument,
-                                                      nColumns);
+        pDocView->priv->m_aTileBuffer = TileBuffer(pDocView->priv->m_pDocument,
+                                                   nColumns);
         gtk_widget_set_size_request(GTK_WIDGET(pDocView),
                                     nDocumentWidthPixels,
                                     nDocumentHeightPixels);
     }
-
     return TRUE;
 }
 
@@ -1236,9 +1417,10 @@ SAL_DLLPUBLIC_EXPORT gboolean lok_doc_view_open_document( LOKDocView* pDocView,
  *
  * Returns: The #LibreOfficeKitDocument instance the widget is currently showing
  */
-SAL_DLLPUBLIC_EXPORT LibreOfficeKitDocument* lok_doc_view_get_document(LOKDocView* pDocView)
+SAL_DLLPUBLIC_EXPORT LibreOfficeKitDocument*
+lok_doc_view_get_document (LOKDocView* pDocView)
 {
-    return pDocView->m_pImpl->m_pDocument;
+    return pDocView->priv->m_pDocument;
 }
 
 /**
@@ -1248,16 +1430,17 @@ SAL_DLLPUBLIC_EXPORT LibreOfficeKitDocument* lok_doc_view_get_document(LOKDocVie
  *
  * Sets the new zoom level for the widget.
  */
-SAL_DLLPUBLIC_EXPORT void lok_doc_view_set_zoom ( LOKDocView* pDocView, float fZoom )
+SAL_DLLPUBLIC_EXPORT void
+lok_doc_view_set_zoom (LOKDocView* pDocView, float fZoom)
 {
-    pDocView->m_pImpl->m_fZoom = fZoom;
-    long nDocumentWidthPixels = twipToPixel(pDocView->m_pImpl->m_nDocumentWidthTwips, fZoom);
-    long nDocumentHeightPixels = twipToPixel(pDocView->m_pImpl->m_nDocumentHeightTwips, fZoom);
+    pDocView->priv->m_fZoom = fZoom;
+    long nDocumentWidthPixels = twipToPixel(pDocView->priv->m_nDocumentWidthTwips, fZoom);
+    long nDocumentHeightPixels = twipToPixel(pDocView->priv->m_nDocumentHeightTwips, fZoom);
     // Total number of columns in this document.
     guint nColumns = ceil((double)nDocumentWidthPixels / nTileSizePixels);
 
-    pDocView->m_pImpl->m_aTileBuffer = TileBuffer(pDocView->m_pImpl->m_pDocument,
-                                                  nColumns);
+    pDocView->priv->m_aTileBuffer = TileBuffer(pDocView->priv->m_pDocument,
+                                               nColumns);
     gtk_widget_set_size_request(GTK_WIDGET(pDocView),
                                 nDocumentWidthPixels,
                                 nDocumentHeightPixels);
@@ -1269,35 +1452,41 @@ SAL_DLLPUBLIC_EXPORT void lok_doc_view_set_zoom ( LOKDocView* pDocView, float fZ
  *
  * Returns: The current zoom factor value in float for pDocView
  */
-SAL_DLLPUBLIC_EXPORT float lok_doc_view_get_zoom ( LOKDocView* pDocView )
+SAL_DLLPUBLIC_EXPORT float
+lok_doc_view_get_zoom (LOKDocView* pDocView)
 {
-    return pDocView->m_pImpl->m_fZoom;
+    return pDocView->priv->m_fZoom;
 }
 
-SAL_DLLPUBLIC_EXPORT int lok_doc_view_get_parts( LOKDocView* pDocView )
+SAL_DLLPUBLIC_EXPORT int
+lok_doc_view_get_parts (LOKDocView* pDocView)
 {
-    return pDocView->m_pImpl->m_pDocument->pClass->getParts( pDocView->m_pImpl->m_pDocument );
+    return pDocView->priv->m_pDocument->pClass->getParts( pDocView->priv->m_pDocument );
 }
 
-SAL_DLLPUBLIC_EXPORT int lok_doc_view_get_part( LOKDocView* pDocView )
+SAL_DLLPUBLIC_EXPORT int
+lok_doc_view_get_part (LOKDocView* pDocView)
 {
-    return pDocView->m_pImpl->m_pDocument->pClass->getPart( pDocView->m_pImpl->m_pDocument );
+    return pDocView->priv->m_pDocument->pClass->getPart( pDocView->priv->m_pDocument );
 }
 
-SAL_DLLPUBLIC_EXPORT void lok_doc_view_set_part( LOKDocView* pDocView, int nPart)
+SAL_DLLPUBLIC_EXPORT void
+lok_doc_view_set_part (LOKDocView* pDocView, int nPart)
 {
-    pDocView->m_pImpl->m_pDocument->pClass->setPart( pDocView->m_pImpl->m_pDocument, nPart );
+    pDocView->priv->m_pDocument->pClass->setPart( pDocView->priv->m_pDocument, nPart );
 }
 
-SAL_DLLPUBLIC_EXPORT char* lok_doc_view_get_part_name( LOKDocView* pDocView, int nPart )
+SAL_DLLPUBLIC_EXPORT char*
+lok_doc_view_get_part_name (LOKDocView* pDocView, int nPart)
 {
-    return pDocView->m_pImpl->m_pDocument->pClass->getPartName( pDocView->m_pImpl->m_pDocument, nPart );
+    return pDocView->priv->m_pDocument->pClass->getPartName( pDocView->priv->m_pDocument, nPart );
 }
 
-SAL_DLLPUBLIC_EXPORT void lok_doc_view_set_partmode( LOKDocView* pDocView,
-                                                    int nPartMode )
+SAL_DLLPUBLIC_EXPORT void
+lok_doc_view_set_partmode(LOKDocView* pDocView,
+                          int nPartMode)
 {
-    pDocView->m_pImpl->m_pDocument->pClass->setPartMode( pDocView->m_pImpl->m_pDocument, nPartMode );
+    pDocView->priv->m_pDocument->pClass->setPartMode( pDocView->priv->m_pDocument, nPartMode );
 }
 
 /**
@@ -1307,19 +1496,20 @@ SAL_DLLPUBLIC_EXPORT void lok_doc_view_set_partmode( LOKDocView* pDocView,
  *
  * Sets the edit-mode for pDocView
  */
-SAL_DLLPUBLIC_EXPORT void lok_doc_view_set_edit( LOKDocView* pDocView,
-                                                gboolean bEdit )
+SAL_DLLPUBLIC_EXPORT void
+lok_doc_view_set_edit(LOKDocView* pDocView,
+                      gboolean bEdit)
 {
-    gboolean bWasEdit = pDocView->m_pImpl->m_bEdit;
+    gboolean bWasEdit = pDocView->priv->m_bEdit;
 
-    if (!pDocView->m_pImpl->m_bEdit && bEdit)
+    if (!pDocView->priv->m_bEdit && bEdit)
         g_info("lok_doc_view_set_edit: entering edit mode");
-    else if (pDocView->m_pImpl->m_bEdit && !bEdit)
+    else if (pDocView->priv->m_bEdit && !bEdit)
     {
         g_info("lok_doc_view_set_edit: leaving edit mode");
-        pDocView->m_pImpl->m_pDocument->pClass->resetSelection(pDocView->m_pImpl->m_pDocument);
+        pDocView->priv->m_pDocument->pClass->resetSelection(pDocView->priv->m_pDocument);
     }
-    pDocView->m_pImpl->m_bEdit = bEdit;
+    pDocView->priv->m_bEdit = bEdit;
     g_signal_emit(pDocView, doc_view_signals[EDIT_CHANGED], 0, bWasEdit);
     gtk_widget_queue_draw(GTK_WIDGET(pDocView));
 }
@@ -1330,20 +1520,39 @@ SAL_DLLPUBLIC_EXPORT void lok_doc_view_set_edit( LOKDocView* pDocView,
  *
  * Returns: %TRUE if the given pDocView is in edit mode.
  */
-SAL_DLLPUBLIC_EXPORT gboolean lok_doc_view_get_edit(LOKDocView* pDocView)
+SAL_DLLPUBLIC_EXPORT gboolean
+lok_doc_view_get_edit (LOKDocView* pDocView)
 {
-    return pDocView->m_pImpl->m_bEdit;
+    return pDocView->priv->m_bEdit;
 }
 
-SAL_DLLPUBLIC_EXPORT void lok_doc_view_post_command(LOKDocView* pDocView, const char* pCommand, const char* pArguments)
+/**
+ * lok_doc_view_post_command:
+ * @pDocView: the #LOKDocView instance
+ * @pCommand: the command to issue to LO core
+ * @pArguments: the arguments to the given command
+ *
+ * This methods forwards your command to LO core.
+*/
+SAL_DLLPUBLIC_EXPORT void
+lok_doc_view_post_command (LOKDocView* pDocView,
+                           const char* pCommand,
+                           const char* pArguments)
 {
-    pDocView->m_pImpl->m_pDocument->pClass->postUnoCommand(pDocView->m_pImpl->m_pDocument, pCommand, pArguments);
+    pDocView->priv->m_pDocument->pClass->postUnoCommand(pDocView->priv->m_pDocument, pCommand, pArguments);
 }
 
-SAL_DLLPUBLIC_EXPORT void lok_doc_view_post_key(GtkWidget* /*pWidget*/, GdkEventKey* pEvent, gpointer pData)
+/**
+ * lok_doc_view_post_key:
+ * @pDocView: the #LOKDocView instance
+ * @pEvent: the #GdkEventKey containing information about the event
+ *
+ * This methods forwards your key events to the LO core.
+*/
+SAL_DLLPUBLIC_EXPORT void
+lok_doc_view_post_key (LOKDocView* pDocView, GdkEvent* pEvent)
 {
-    LOKDocView* pDocView = static_cast<LOKDocView *>(pData);
-    pDocView->m_pImpl->signalKey(pEvent);
+    signalKey(pDocView, pEvent);
 }
 
 /**
@@ -1355,9 +1564,10 @@ SAL_DLLPUBLIC_EXPORT void lok_doc_view_post_key(GtkWidget* /*pWidget*/, GdkEvent
  *
  * Returns: The corresponding value in twips
  */
-SAL_DLLPUBLIC_EXPORT float lok_doc_view_pixel_to_twip(LOKDocView* pDocView, float fInput)
+SAL_DLLPUBLIC_EXPORT float
+lok_doc_view_pixel_to_twip (LOKDocView* pDocView, float fInput)
 {
-    return pixelToTwip(fInput, pDocView->m_pImpl->m_fZoom);
+    return pixelToTwip(fInput, pDocView->priv->m_fZoom);
 }
 
 /**
@@ -1369,10 +1579,10 @@ SAL_DLLPUBLIC_EXPORT float lok_doc_view_pixel_to_twip(LOKDocView* pDocView, floa
  *
  * Returns: The corresponding value in pixels
  */
-SAL_DLLPUBLIC_EXPORT float lok_doc_view_twip_to_pixel(LOKDocView* pDocView, float fInput)
+SAL_DLLPUBLIC_EXPORT float
+lok_doc_view_twip_to_pixel (LOKDocView* pDocView, float fInput)
 {
-    return twipToPixel(fInput, pDocView->m_pImpl->m_fZoom);
+    return twipToPixel(fInput, pDocView->priv->m_fZoom);
 }
 
-
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
-- 
2.12.0