Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/libreofficekit/source/gtk/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 148 kB image not shown  

Quelle  lokdocview.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */


#include <sal/types.h>
#include <math.h>
#include <string.h>
#include <memory>
#include <utility>
#include <vector>
#include <string>
#include <sstream>
#include <mutex>
#include <boost/property_tree/json_parser.hpp>

#include <com/sun/star/awt/Key.hpp>
#include <LibreOfficeKit/LibreOfficeKit.h>
#include <LibreOfficeKit/LibreOfficeKitInit.h>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <LibreOfficeKit/LibreOfficeKitGtk.h>
#include <vcl/event.hxx>

#include "tilebuffer.hxx"

#if !GLIB_CHECK_VERSION(2,32,0)
#define G_SOURCE_REMOVE FALSE
#define G_SOURCE_CONTINUE TRUE
#endif
#if !GLIB_CHECK_VERSION(2,40,0)
#define g_info(...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__)
#endif

// Cursor bitmaps from the installation set.
#define CURSOR_HANDLE_DIR "/../share/libreofficekit/"
// Number of handles around a graphic selection.
#define GRAPHIC_HANDLE_COUNT 8
// Maximum Zoom allowed
#define MAX_ZOOM 5.0f
// Minimum Zoom allowed
#define MIN_ZOOM 0.25f

/// This is expected to be locked during setView(), doSomethingElse() LOK calls.
static std::mutex g_aLOKMutex;

namespace {

/// Same as a GdkRectangle, but also tracks in which part the rectangle is.
struct ViewRectangle
{
    int m_nPart;
    GdkRectangle m_aRectangle;

    ViewRectangle(int nPart = 0, const GdkRectangle& rRectangle = GdkRectangle())
        : m_nPart(nPart),
        m_aRectangle(rRectangle)
    {
    }
};

/// Same as a list of GdkRectangles, but also tracks in which part the rectangle is.
struct ViewRectangles
{
    int m_nPart;
    std::vector<GdkRectangle> m_aRectangles;

    ViewRectangles(int nPart = 0, std::vector<GdkRectangle>&& rRectangles = std::vector<GdkRectangle>())
        : m_nPart(nPart),
        m_aRectangles(std::move(rRectangles))
    {
    }
};

/// Private struct used by this GObject type
struct LOKDocViewPrivateImpl
{
    std::string m_aLOPath;
    std::string m_aUserProfileURL;
    std::string m_aDocPath;
    std::string m_aRenderingArguments;
    gdouble m_nLoadProgress;
    bool m_bIsLoading;
    bool m_bInit; // initializeForRendering() has been called
    bool m_bCanZoomIn;
    bool m_bCanZoomOut;
    bool m_bUnipoll;
    LibreOfficeKit* m_pOffice;
    LibreOfficeKitDocument* m_pDocument;

    std::unique_ptr<TileBuffer> m_pTileBuffer;
    GThreadPool* lokThreadPool;

    gfloat m_fZoom;
    glong m_nDocumentWidthTwips;
    glong m_nDocumentHeightTwips;
    /// View or edit mode.
    bool m_bEdit;
    /// LOK Features
    guint64 m_nLOKFeatures;
    /// Number of parts in currently loaded document
    gint m_nParts;
    /// Position and size of the visible cursor.
    GdkRectangle m_aVisibleCursor;
    /// Position and size of the view cursors. The current view can only see
    /// them, can't modify them. Key is the view id.
    std::map<int, ViewRectangle> m_aViewCursors;
    /// Cursor overlay is visible or hidden (for blinking).
    bool m_bCursorOverlayVisible;
    /// Cursor is visible or hidden (e.g. for graphic selection).
    bool m_bCursorVisible;
    /// Visibility of view selections. The current view can only see / them,
    /// can't modify them. Key is the view id.
    std::map<intbool> m_aViewCursorVisibilities;
    /// Time of the last button press.
    guint32 m_nLastButtonPressTime;
    /// Time of the last button release.
    guint32 m_nLastButtonReleaseTime;
    /// Last pressed button (left, right, middle)
    guint32 m_nLastButtonPressed;
    /// Key modifier (ctrl, atl, shift)
    guint32 m_nKeyModifier;
    /// Rectangles of the current text selection.
    std::vector<GdkRectangle> m_aTextSelectionRectangles;
    /// Rectangles of the current content control.
    std::vector<GdkRectangle> m_aContentControlRectangles;
    /// Alias/title of the current content control.
    std::string m_aContentControlAlias;
    /// Rectangles of view selections. The current view can only see
    /// them, can't modify them. Key is the view id.
    std::map<int, ViewRectangles> m_aTextViewSelectionRectangles;
    /// Position and size of the selection start (as if there would be a cursor caret there).
    GdkRectangle m_aTextSelectionStart;
    /// Position and size of the selection end.
    GdkRectangle m_aTextSelectionEnd;
    GdkRectangle m_aGraphicSelection;
    /// Position and size of the graphic view selections. The current view can only
    /// see them, can't modify them. Key is the view id.
    std::map<int, ViewRectangle> m_aGraphicViewSelections;
    GdkRectangle m_aCellCursor;
    /// Position and size of the cell view cursors. The current view can only
    /// see them, can't modify them. Key is the view id.
    std::map<int, ViewRectangle> m_aCellViewCursors;
    bool m_bInDragGraphicSelection;
    /// Position, size and color of the reference marks. The current view can only
    /// see them, can't modify them. Key is the view id.
    std::vector<std::pair<ViewRectangle, sal_uInt32>> m_aReferenceMarks;

    /// @name Start/middle/end handle.
    ///@{
    /// Bitmap of the text selection start handle.
    cairo_surface_t* m_pHandleStart;
    /// 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;
    /// 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;
    /// 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;
    ///@}

    /// @name Graphic handles.
    ///@{
    /// 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];
    ///@}

    /// View ID, returned by createView() or 0 by default.
    int m_nViewId;

    /// Cached part ID, returned by getPart().
    int m_nPartId;

    /// Cached document type, returned by getDocumentType().
    LibreOfficeKitDocumentType m_eDocumentType;

    /// Contains a freshly set zoom level: logic size of a tile.
    /// It gets reset back to 0 when LOK was informed about this zoom change.
    int m_nTileSizeTwips;

    GdkRectangle m_aVisibleArea;
    bool m_bVisibleAreaSet;

    /// Event source ID for handleTimeout() of this widget.
    guint m_nTimeoutId;

    /// Rectangles of view locks. The current view can only see
    /// them, can't modify them. Key is the view id.
    std::map<int, ViewRectangle> m_aViewLockRectangles;

    LOKDocViewPrivateImpl()
        : m_nLoadProgress(0),
        m_bIsLoading(false),
        m_bInit(false),
        m_bCanZoomIn(true),
        m_bCanZoomOut(true),
        m_bUnipoll(false),
        m_pOffice(nullptr),
        m_pDocument(nullptr),
        lokThreadPool(nullptr),
        m_fZoom(0),
        m_nDocumentWidthTwips(0),
        m_nDocumentHeightTwips(0),
        m_bEdit(false),
        m_nLOKFeatures(0),
        m_nParts(0),
        m_aVisibleCursor({0, 0, 0, 0}),
        m_bCursorOverlayVisible(false),
        m_bCursorVisible(true),
        m_nLastButtonPressTime(0),
        m_nLastButtonReleaseTime(0),
        m_nLastButtonPressed(0),
        m_nKeyModifier(0),
        m_aTextSelectionStart({0, 0, 0, 0}),
        m_aTextSelectionEnd({0, 0, 0, 0}),
        m_aGraphicSelection({0, 0, 0, 0}),
        m_aCellCursor({0, 0, 0, 0}),
        m_bInDragGraphicSelection(false),
        m_pHandleStart(nullptr),
        m_aHandleStartRect({0, 0, 0, 0}),
        m_bInDragStartHandle(false),
        m_pHandleMiddle(nullptr),
        m_aHandleMiddleRect({0, 0, 0, 0}),
        m_bInDragMiddleHandle(false),
        m_pHandleEnd(nullptr),
        m_aHandleEndRect({0, 0, 0, 0}),
        m_bInDragEndHandle(false),
        m_nViewId(0),
        m_nPartId(0),
        m_eDocumentType(LOK_DOCTYPE_OTHER),
        m_nTileSizeTwips(0),
        m_aVisibleArea({0, 0, 0, 0}),
        m_bVisibleAreaSet(false),
        m_nTimeoutId(0)
    {
        memset(&m_aGraphicHandleRects, 0, sizeof(m_aGraphicHandleRects));
        memset(&m_bInDragGraphicHandles, 0, sizeof(m_bInDragGraphicHandles));
    }

    ~LOKDocViewPrivateImpl()
    {
        if (m_nTimeoutId)
            g_source_remove(m_nTimeoutId);
    }
};

// Must be run with g_aLOKMutex locked
void setDocumentView(LibreOfficeKitDocument* pDoc, int viewId)
{
    assert(pDoc);
    std::stringstream ss;
    ss << "lok::Document::setView(" << viewId << ")";
    g_info("%s", ss.str().c_str());
    pDoc->pClass->setView(pDoc, viewId);
}
}

/// Wrapper around LOKDocViewPrivateImpl, managed by malloc/memset/free.
struct _LOKDocViewPrivate
{
    LOKDocViewPrivateImpl* m_pImpl;

    LOKDocViewPrivateImpl* operator->()
    {
        return m_pImpl;
    }
};

enum
{
    LOAD_CHANGED,
    EDIT_CHANGED,
    COMMAND_CHANGED,
    SEARCH_NOT_FOUND,
    PART_CHANGED,
    SIZE_CHANGED,
    HYPERLINK_CLICKED,
    CURSOR_CHANGED,
    SEARCH_RESULT_COUNT,
    COMMAND_RESULT,
    ADDRESS_CHANGED,
    FORMULA_CHANGED,
    TEXT_SELECTION,
    CONTENT_CONTROL,
    PASSWORD_REQUIRED,
    COMMENT,
    RULER,
    WINDOW,
    INVALIDATE_HEADER,

    LAST_SIGNAL
};

enum
{
    PROP_0,

    PROP_LO_PATH,
    PROP_LO_UNIPOLL,
    PROP_LO_POINTER,
    PROP_USER_PROFILE_URL,
    PROP_DOC_PATH,
    PROP_DOC_POINTER,
    PROP_EDITABLE,
    PROP_LOAD_PROGRESS,
    PROP_ZOOM,
    PROP_IS_LOADING,
    PROP_IS_INITIALIZED,
    PROP_DOC_WIDTH,
    PROP_DOC_HEIGHT,
    PROP_CAN_ZOOM_IN,
    PROP_CAN_ZOOM_OUT,
    PROP_DOC_PASSWORD,
    PROP_DOC_PASSWORD_TO_MODIFY,
    PROP_TILED_ANNOTATIONS,

    PROP_LAST
};

static guint doc_view_signals[LAST_SIGNAL] = { 0 };
static GParamSpec *properties[PROP_LAST] = { nullptr };

static void lok_doc_view_initable_iface_init (GInitableIface *iface);
static void callbackWorker (int nType, const char* pPayload, void* pData);
static void updateClientZoom (LOKDocView *pDocView);

#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#if defined __clang__
#if __has_warning("-Wdeprecated-volatile")
#pragma clang diagnostic ignored "-Wdeprecated-volatile"
#endif
#endif
#endif
G_DEFINE_TYPE_WITH_CODE (LOKDocView, lok_doc_view, GTK_TYPE_DRAWING_AREA,
                         G_ADD_PRIVATE (LOKDocView)
                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, lok_doc_view_initable_iface_init));
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

static LOKDocViewPrivate& getPrivate(LOKDocView* pDocView)
{
    LOKDocViewPrivate* priv = static_cast<LOKDocViewPrivate*>(lok_doc_view_get_instance_private(pDocView));
    return *priv;
}

namespace {

/// Helper struct used to pass the data from soffice thread -> main thread.
struct CallbackData
{
    int m_nType;
    std::string m_aPayload;
    LOKDocView* m_pDocView;

    CallbackData(int nType, std::string aPayload, LOKDocView* pDocView)
        : m_nType(nType),
          m_aPayload(std::move(aPayload)),
          m_pDocView(pDocView) {}
};

}

static void
LOKPostCommand (LOKDocView* pDocView,
                const gchar* pCommand,
                const gchar* pArguments,
                bool bNotifyWhenFinished)
{
    LOKDocViewPrivate& priv = getPrivate(pDocView);
    GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
    LOEvent* pLOEvent = new LOEvent(LOK_POST_COMMAND);
    GError* error = nullptr;
    pLOEvent->m_pCommand = g_strdup(pCommand);
    pLOEvent->m_pArguments  = g_strdup(pArguments);
    pLOEvent->m_bNotifyWhenFinished = bNotifyWhenFinished;

    g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
    g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
    if (error != nullptr)
    {
        g_warning("Unable to call LOK_POST_COMMAND: %s", error->message);
        g_clear_error(&error);
    }
    g_object_unref(task);
}

static void
doSearch(LOKDocView* pDocView, const char* pText, bool bBackwards, bool highlightAll)
{
    LOKDocViewPrivate& priv = getPrivate(pDocView);
    if (!priv->m_pDocument)
        return;

    boost::property_tree::ptree aTree;
    GtkWidget* drawingWidget = GTK_WIDGET(pDocView);
    GdkWindow* drawingWindow = gtk_widget_get_window(drawingWidget);
    if (!drawingWindow)
        return;
    std::shared_ptr<cairo_region_t> cairoVisRegion( gdk_window_get_visible_region(drawingWindow),
                                                    cairo_region_destroy);
    cairo_rectangle_int_t cairoVisRect;
    cairo_region_get_rectangle(cairoVisRegion.get(), 0, &cairoVisRect);
    int x = pixelToTwip (cairoVisRect.x, priv->m_fZoom);
    int y = pixelToTwip (cairoVisRect.y, priv->m_fZoom);

    aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchString/type"'/'), "string");
    aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchString/value"'/'), pText);
    aTree.put(boost::property_tree::ptree::path_type("SearchItem.Backward/type"'/')"boolean");
    aTree.put(boost::property_tree::ptree::path_type("SearchItem.Backward/value"'/'), bBackwards);
    if (highlightAll)
    {
        aTree.put(boost::property_tree::ptree::path_type("SearchItem.Command/type"'/'), "unsigned short");
        // SvxSearchCmd::FIND_ALL
        aTree.put(boost::property_tree::ptree::path_type("SearchItem.Command/value"'/')"1");
    }

    aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointX/type"'/'), "long");
    aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointX/value"'/'), x);
    aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointY/type"'/'), "long");
    aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointY/value"'/'), y);

    std::stringstream aStream;
    boost::property_tree::write_json(aStream, aTree);

    LOKPostCommand (pDocView, ".uno:ExecuteSearch", aStream.str().c_str(), false);
}

static bool
isEmptyRectangle(const GdkRectangle& rRectangle)
{
    return rRectangle.x == 0 && rRectangle.y == 0 && rRectangle.width == 0 && rRectangle.height == 0;
}

/// if handled, returns TRUE else FALSE
static bool
handleTextSelectionOnButtonPress(GdkRectangle& aClick, LOKDocView* pDocView) {
    LOKDocViewPrivate& priv = getPrivate(pDocView);

    if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleStartRect, nullptr))
    {
        g_info("LOKDocView_Impl::signalButton: start of drag start handle");
        priv->m_bInDragStartHandle = true;
        return true;
    }
    else if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleMiddleRect, nullptr))
    {
        g_info("LOKDocView_Impl::signalButton: start of drag middle handle");
        priv->m_bInDragMiddleHandle = true;
        return true;
    }
    else if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleEndRect, nullptr))
    {
        g_info("LOKDocView_Impl::signalButton: start of drag end handle");
        priv->m_bInDragEndHandle = true;
        return true;
    }

    return false;
}

/// if handled, returns TRUE else FALSE
static bool
handleGraphicSelectionOnButtonPress(GdkRectangle& aClick, LOKDocView* pDocView) {
    LOKDocViewPrivate& priv = getPrivate(pDocView);
    GError* error = nullptr;

    for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
    {
        if (gdk_rectangle_intersect(&aClick, &priv->m_aGraphicHandleRects[i], nullptr))
        {
            g_info("LOKDocView_Impl::signalButton: start of drag graphic handle #%d", i);
            priv->m_bInDragGraphicHandles[i] = true;

            GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
            LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
            pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_START;
            pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(priv->m_aGraphicHandleRects[i].x + priv->m_aGraphicHandleRects[i].width / 2, priv->m_fZoom);
            pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(priv->m_aGraphicHandleRects[i].y + priv->m_aGraphicHandleRects[i].height / 2, priv->m_fZoom);
            g_task_set_task_data(task, pLOEvent, LOEvent::destroy);

            g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
            if (error != nullptr)
            {
                g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
                g_clear_error(&error);
            }
            g_object_unref(task);

            return true;
        }
    }

    return false;
}

/// if handled, returns TRUE else FALSE
static bool
handleTextSelectionOnButtonRelease(LOKDocView* pDocView) {
    LOKDocViewPrivate& priv = getPrivate(pDocView);

    if (priv->m_bInDragStartHandle)
    {
        g_info("LOKDocView_Impl::signalButton: end of drag start handle");
        priv->m_bInDragStartHandle = false;
        return true;
    }
    else if (priv->m_bInDragMiddleHandle)
    {
        g_info("LOKDocView_Impl::signalButton: end of drag middle handle");
        priv->m_bInDragMiddleHandle = false;
        return true;
    }
    else if (priv->m_bInDragEndHandle)
    {
        g_info("LOKDocView_Impl::signalButton: end of drag end handle");
        priv->m_bInDragEndHandle = false;
        return true;
    }

    return false;
}

/// if handled, returns TRUE else FALSE
static bool
handleGraphicSelectionOnButtonRelease(LOKDocView* pDocView, GdkEventButton* pEvent) {
    LOKDocViewPrivate& priv = getPrivate(pDocView);
    GError* error = nullptr;

    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;

            GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
            LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
            pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_END;
            pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(pEvent->x, priv->m_fZoom);
            pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(pEvent->y, priv->m_fZoom);
            g_task_set_task_data(task, pLOEvent, LOEvent::destroy);

            g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
            if (error != nullptr)
            {
                g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
                g_clear_error(&error);
            }
            g_object_unref(task);

            return true;
        }
    }

    if (!priv->m_bInDragGraphicSelection)
        return false;

    g_info("LOKDocView_Impl::signalButton: end of drag graphic selection");
    priv->m_bInDragGraphicSelection = false;

    GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
    LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
    pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_END;
    pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(pEvent->x, priv->m_fZoom);
    pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(pEvent->y, priv->m_fZoom);
    g_task_set_task_data(task, pLOEvent, LOEvent::destroy);

    g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
    if (error != nullptr)
    {
        g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
        g_clear_error(&error);
    }
    g_object_unref(task);

    return true;
}

static void
postKeyEventInThread(gpointer data)
{
    GTask* task = G_TASK(data);
    LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
    LOKDocViewPrivate& priv = getPrivate(pDocView);
    LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
    gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
    gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;

    std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
    setDocumentView(priv->m_pDocument, priv->m_nViewId);
    std::stringstream ss;

    if (priv->m_nTileSizeTwips)
    {
        ss.str(std::string());
        ss << "lok::Document::setClientZoom(" << nTileSizePixelsScaled << ", " << nTileSizePixelsScaled << ", " << priv->m_nTileSizeTwips << ", " << priv->m_nTileSizeTwips << ")";
        g_info("%s", ss.str().c_str());
        priv->m_pDocument->pClass->setClientZoom(priv->m_pDocument,
                                                 nTileSizePixelsScaled,
                                                 nTileSizePixelsScaled,
                                                 priv->m_nTileSizeTwips,
                                                 priv->m_nTileSizeTwips);
        priv->m_nTileSizeTwips = 0;
    }
    if (priv->m_bVisibleAreaSet)
    {
        ss.str(std::string());
        ss << "lok::Document::setClientVisibleArea(" << priv->m_aVisibleArea.x << ", " << priv->m_aVisibleArea.y << ", ";
        ss << priv->m_aVisibleArea.width << ", " << priv->m_aVisibleArea.height << ")";
        g_info("%s", ss.str().c_str());
        priv->m_pDocument->pClass->setClientVisibleArea(priv->m_pDocument,
                                                        priv->m_aVisibleArea.x,
                                                        priv->m_aVisibleArea.y,
                                                        priv->m_aVisibleArea.width,
                                                        priv->m_aVisibleArea.height);
        priv->m_bVisibleAreaSet = false;
    }

    ss.str(std::string());
    ss << "lok::Document::postKeyEvent(" << pLOEvent->m_nKeyEvent << ", " << pLOEvent->m_nCharCode << ", " << pLOEvent->m_nKeyCode << ")";
    g_info("%s", ss.str().c_str());
    priv->m_pDocument->pClass->postKeyEvent(priv->m_pDocument,
                                            pLOEvent->m_nKeyEvent,
                                            pLOEvent->m_nCharCode,
                                            pLOEvent->m_nKeyCode);
}

static gboolean
signalKey (GtkWidget* pWidget, GdkEventKey* pEvent)
{
    LOKDocView* pDocView = LOK_DOC_VIEW(pWidget);
    LOKDocViewPrivate& priv = getPrivate(pDocView);
    int nCharCode = 0;
    int nKeyCode = 0;
    GError* error = nullptr;

    if (!priv->m_bEdit)
    {
        g_info("signalKey: not in edit mode, ignore");
        return FALSE;
    }

    priv->m_nKeyModifier &= KEY_MOD2;
    switch (pEvent->keyval)
    {
    case GDK_KEY_BackSpace:
        nKeyCode = css::awt::Key::BACKSPACE;
        break;
    case GDK_KEY_Delete:
        nKeyCode = css::awt::Key::DELETE;
        break;
    case GDK_KEY_Return:
    case GDK_KEY_KP_Enter:
        nKeyCode = css::awt::Key::RETURN;
        break;
    case GDK_KEY_Escape:
        nKeyCode = css::awt::Key::ESCAPE;
        break;
    case GDK_KEY_Tab:
        nKeyCode = css::awt::Key::TAB;
        break;
    case GDK_KEY_Down:
        nKeyCode = css::awt::Key::DOWN;
        break;
    case GDK_KEY_Up:
        nKeyCode = css::awt::Key::UP;
        break;
    case GDK_KEY_Left:
        nKeyCode = css::awt::Key::LEFT;
        break;
    case GDK_KEY_Right:
        nKeyCode = css::awt::Key::RIGHT;
        break;
    case GDK_KEY_Page_Down:
        nKeyCode = css::awt::Key::PAGEDOWN;
        break;
    case GDK_KEY_Page_Up:
        nKeyCode = css::awt::Key::PAGEUP;
        break;
    case GDK_KEY_Insert:
        nKeyCode = css::awt::Key::INSERT;
        break;
    case GDK_KEY_Shift_L:
    case GDK_KEY_Shift_R:
        if (pEvent->type == GDK_KEY_PRESS)
            priv->m_nKeyModifier |= KEY_SHIFT;
        break;
    case GDK_KEY_Control_L:
    case GDK_KEY_Control_R:
        if (pEvent->type == GDK_KEY_PRESS)
            priv->m_nKeyModifier |= KEY_MOD1;
        break;
    case GDK_KEY_Alt_L:
    case GDK_KEY_Alt_R:
        if (pEvent->type == GDK_KEY_PRESS)
            priv->m_nKeyModifier |= KEY_MOD2;
        else
            priv->m_nKeyModifier &= ~KEY_MOD2;
        break;
    default:
        if (pEvent->keyval >= GDK_KEY_F1 && pEvent->keyval <= GDK_KEY_F26)
            nKeyCode = css::awt::Key::F1 + (pEvent->keyval - GDK_KEY_F1);
        else
            nCharCode = gdk_keyval_to_unicode(pEvent->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)
        nKeyCode |= KEY_SHIFT;

    if (pEvent->state & GDK_CONTROL_MASK)
        nKeyCode |= KEY_MOD1;

    if (priv->m_nKeyModifier & KEY_MOD2)
        nKeyCode |= KEY_MOD2;

    if (nKeyCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2)) {
        if (pEvent->keyval >= GDK_KEY_a && pEvent->keyval <= GDK_KEY_z)
        {
            nKeyCode |= 512 + (pEvent->keyval - GDK_KEY_a);
        }
        else if (pEvent->keyval >= GDK_KEY_A && pEvent->keyval <= GDK_KEY_Z) {
                nKeyCode |= 512 + (pEvent->keyval - GDK_KEY_A);
        }
        else if (pEvent->keyval >= GDK_KEY_0 && pEvent->keyval <= GDK_KEY_9) {
                nKeyCode |= 256 + (pEvent->keyval - GDK_KEY_0);
        }
    }

    GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
    LOEvent* pLOEvent = new LOEvent(LOK_POST_KEY);
    pLOEvent->m_nKeyEvent = pEvent->type == GDK_KEY_RELEASE ? LOK_KEYEVENT_KEYUP : LOK_KEYEVENT_KEYINPUT;
    pLOEvent->m_nCharCode = nCharCode;
    pLOEvent->m_nKeyCode  = nKeyCode;
    g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
    g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
    if (error != nullptr)
    {
        g_warning("Unable to call LOK_POST_KEY: %s", error->message);
        g_clear_error(&error);
    }
    g_object_unref(task);

    return FALSE;
}

static gboolean
handleTimeout (gpointer pData)
{
    LOKDocView* pDocView = LOK_DOC_VIEW (pData);
    LOKDocViewPrivate& priv = getPrivate(pDocView);

    if (priv->m_bEdit)
    {
        if (priv->m_bCursorOverlayVisible)
            priv->m_bCursorOverlayVisible = false;
        else
            priv->m_bCursorOverlayVisible = true;
        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
    }

    return G_SOURCE_CONTINUE;
}

static void
commandChanged(LOKDocView* pDocView, const std::string& rString)
{
    g_signal_emit(pDocView, doc_view_signals[COMMAND_CHANGED], 0, rString.c_str());
}

static void
searchNotFound(LOKDocView* pDocView, const std::string& rString)
{
    g_signal_emit(pDocView, doc_view_signals[SEARCH_NOT_FOUND], 0, rString.c_str());
}

static void searchResultCount(LOKDocView* pDocView, const std::string& rString)
{
    g_signal_emit(pDocView, doc_view_signals[SEARCH_RESULT_COUNT], 0, rString.c_str());
}

static void commandResult(LOKDocView* pDocView, const std::string& rString)
{
    g_signal_emit(pDocView, doc_view_signals[COMMAND_RESULT], 0, rString.c_str());
}

static void addressChanged(LOKDocView* pDocView, const std::string& rString)
{
    g_signal_emit(pDocView, doc_view_signals[ADDRESS_CHANGED], 0, rString.c_str());
}

static void formulaChanged(LOKDocView* pDocView, const std::string& rString)
{
    g_signal_emit(pDocView, doc_view_signals[FORMULA_CHANGED], 0, rString.c_str());
}

static void reportError(LOKDocView* /*pDocView*/, const std::string& rString)
{
    GtkWidget *dialog = gtk_message_dialog_new(nullptr,
            GTK_DIALOG_DESTROY_WITH_PARENT,
            GTK_MESSAGE_ERROR,
            GTK_BUTTONS_CLOSE,
            "%s",
            rString.c_str());
    gtk_dialog_run(GTK_DIALOG(dialog));
    gtk_widget_destroy(dialog);
}

static void
setPart(LOKDocView* pDocView, const std::string& rString)
{
    LOKDocViewPrivate& priv = getPrivate(pDocView);
    priv->m_nPartId = std::stoi(rString);
    g_signal_emit(pDocView, doc_view_signals[PART_CHANGED], 0, priv->m_nPartId);
}

static void
hyperlinkClicked(LOKDocView* pDocView, const std::string& rString)
{
    g_signal_emit(pDocView, doc_view_signals[HYPERLINK_CLICKED], 0, rString.c_str());
}

/// Trigger a redraw, invoked on the main thread by other functions running in a thread.
static gboolean queueDraw(gpointer pData)
{
    GtkWidget* pWidget = static_cast<GtkWidget*>(pData);

    gtk_widget_queue_draw(pWidget);

    return G_SOURCE_REMOVE;
}

/// Looks up the author string from initializeForRendering()'s rendering arguments.
static std::string getAuthorRenderingArgument(LOKDocViewPrivate& priv)
{
    std::stringstream aStream;
    aStream << priv->m_aRenderingArguments;
    boost::property_tree::ptree aTree;
    boost::property_tree::read_json(aStream, aTree);
    std::string aRet;
    for (const auto& rPair : aTree)
    {
        if (rPair.first == ".uno:Author")
        {
            aRet = rPair.second.get<std::string>("value");
            break;
        }
    }
    return aRet;
}

/// Author string <-> View ID map
static std::map<std::string, int> g_aAuthorViews;

static void refreshSize(LOKDocView* pDocView)
{
    LOKDocViewPrivate& priv = getPrivate(pDocView);

    priv->m_pDocument->pClass->getDocumentSize(priv->m_pDocument, &priv->m_nDocumentWidthTwips, &priv->m_nDocumentHeightTwips);
    float zoom = priv->m_fZoom;
    gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
    gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
    long nDocumentWidthTwips = priv->m_nDocumentWidthTwips;
    long nDocumentHeightTwips = priv->m_nDocumentHeightTwips;
    long nDocumentWidthPixels = twipToPixel(nDocumentWidthTwips, zoom);
    long nDocumentHeightPixels = twipToPixel(nDocumentHeightTwips, zoom);

    // Total number of columns in this document.
    guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixelsScaled);
    priv->m_pTileBuffer = std::make_unique<TileBuffer>(nColumns, nScaleFactor);
    gtk_widget_set_size_request(GTK_WIDGET(pDocView),
                                nDocumentWidthPixels,
                                nDocumentHeightPixels);
}

/// Set up LOKDocView after the document is loaded, invoked on the main thread by openDocumentInThread() running in a thread.
static gboolean postDocumentLoad(gpointer pData)
{
    LOKDocView* pLOKDocView = static_cast<LOKDocView*>(pData);
    LOKDocViewPrivate& priv = getPrivate(pLOKDocView);

    std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
    priv->m_pDocument->pClass->initializeForRendering(priv->m_pDocument, priv->m_aRenderingArguments.c_str());
    // This returns the view id of the "current" view, but sadly if you load multiple documents that
    // is apparently not a view showing the most recently loaded document. Not much we can do here,
    // though. If that is fixed, this comment becomes incorrect.
    priv->m_nViewId = priv->m_pDocument->pClass->getView(priv->m_pDocument);
    g_aAuthorViews[getAuthorRenderingArgument(priv)] = priv->m_nViewId;
    priv->m_pDocument->pClass->registerCallback(priv->m_pDocument, callbackWorker, pLOKDocView);
    priv->m_nParts = priv->m_pDocument->pClass->getParts(priv->m_pDocument);
    aGuard.unlock();
    priv->m_nTimeoutId = g_timeout_add(600, handleTimeout, pLOKDocView);

    refreshSize(pLOKDocView);

    gtk_widget_set_can_focus(GTK_WIDGET(pLOKDocView), true);
    gtk_widget_grab_focus(GTK_WIDGET(pLOKDocView));
    lok_doc_view_set_zoom(pLOKDocView, 1.0);

    // we are completely loaded
    priv->m_bInit = true;
    g_object_notify_by_pspec(G_OBJECT(pLOKDocView), properties[PROP_IS_INITIALIZED]);

    return G_SOURCE_REMOVE;
}

/// Implementation of the global callback handler, invoked by globalCallback();
static gboolean
globalCallback (gpointer pData)
{
    CallbackData* pCallback = static_cast<CallbackData*>(pData);
    LOKDocViewPrivate& priv = getPrivate(pCallback->m_pDocView);
    bool bModify = false;

    switch (pCallback->m_nType)
    {
    case LOK_CALLBACK_STATUS_INDICATOR_START:
    {
        priv->m_nLoadProgress = 0.0;
        g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, 0.0);
    }
    break;
    case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
    {
        priv->m_nLoadProgress = static_cast<gdouble>(std::stoi(pCallback->m_aPayload)/100.0);
        g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, priv->m_nLoadProgress);
    }
    break;
    case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
    {
        priv->m_nLoadProgress = 1.0;
        g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, 1.0);
    }
    break;
    case LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY:
        bModify = true;
        [[fallthrough]];
    case LOK_CALLBACK_DOCUMENT_PASSWORD:
    {
        char const*const pURL(pCallback->m_aPayload.c_str());
        g_signal_emit (pCallback->m_pDocView, doc_view_signals[PASSWORD_REQUIRED], 0, pURL, bModify);
    }
    break;
    case LOK_CALLBACK_ERROR:
    {
        reportError(pCallback->m_pDocView, pCallback->m_aPayload);
    }
    break;
    case LOK_CALLBACK_SIGNATURE_STATUS:
    {
        // TODO
    }
    break;
    default:
        g_assert(false);
        break;
    }
    delete pCallback;

    return G_SOURCE_REMOVE;
}

static void
globalCallbackWorker(int nType, const char* pPayload, void* pData)
{
    LOKDocView* pDocView = LOK_DOC_VIEW (pData);

    CallbackData* pCallback = new CallbackData(nType, pPayload ? pPayload : "(nil)", pDocView);
    g_info("LOKDocView_Impl::globalCallbackWorkerImpl: %s, '%s'", lokCallbackTypeToString(nType), pPayload);
    gdk_threads_add_idle(globalCallback, pCallback);
}

static GdkRectangle
payloadToRectangle (LOKDocView* pDocView, const char* pPayload)
{
    LOKDocViewPrivate& priv = getPrivate(pDocView);
    GdkRectangle aRet;
    // x, y, width, height, part number.
    gchar** ppCoordinates = g_strsplit(pPayload, ", ", 5);
    gchar** ppCoordinate = ppCoordinates;

    aRet.width = aRet.height = aRet.x = aRet.y = 0;

    if (!*ppCoordinate)
    {
        g_strfreev(ppCoordinates);
        return aRet;
    }
    aRet.x = atoi(*ppCoordinate);
    if (aRet.x < 0)
        aRet.x = 0;
    ++ppCoordinate;
    if (!*ppCoordinate)
    {
        g_strfreev(ppCoordinates);
        return aRet;
    }
    aRet.y = atoi(*ppCoordinate);
    if (aRet.y < 0)
        aRet.y = 0;
    ++ppCoordinate;
    if (!*ppCoordinate)
    {
        g_strfreev(ppCoordinates);
        return aRet;
    }
    long l = atol(*ppCoordinate);
    if (l > std::numeric_limits<int>::max())
        aRet.width = std::numeric_limits<int>::max();
    else
        aRet.width = l;
    if (aRet.x + aRet.width > priv->m_nDocumentWidthTwips)
        aRet.width = priv->m_nDocumentWidthTwips - aRet.x;
    ++ppCoordinate;
    if (!*ppCoordinate)
    {
        g_strfreev(ppCoordinates);
        return aRet;
    }
    l = atol(*ppCoordinate);
    if (l > std::numeric_limits<int>::max())
        aRet.height = std::numeric_limits<int>::max();
    else
        aRet.height = l;
    if (aRet.y + aRet.height > priv->m_nDocumentHeightTwips)
        aRet.height = priv->m_nDocumentHeightTwips - aRet.y;

    g_strfreev(ppCoordinates);
    return aRet;
}

static std::vector<GdkRectangle>
payloadToRectangles(LOKDocView* pDocView, const char* pPayload)
{
    std::vector<GdkRectangle> aRet;

    if (g_strcmp0(pPayload, "EMPTY") == 0)
       return aRet;

    gchar** ppRectangles = g_strsplit(pPayload, "; ", 0);
    for (gchar** ppRectangle = ppRectangles; *ppRectangle; ++ppRectangle)
        aRet.push_back(payloadToRectangle(pDocView, *ppRectangle));
    g_strfreev(ppRectangles);

    return aRet;
}


static void
setTilesInvalid (LOKDocView* pDocView, const GdkRectangle& rRectangle)
{
    LOKDocViewPrivate& priv = getPrivate(pDocView);
    GdkRectangle aRectanglePixels;
    GdkPoint aStart, aEnd;
    gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
    gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;

    aRectanglePixels.x = twipToPixel(rRectangle.x, priv->m_fZoom) * nScaleFactor;
    aRectanglePixels.y = twipToPixel(rRectangle.y, priv->m_fZoom) * nScaleFactor;
    aRectanglePixels.width = twipToPixel(rRectangle.width, priv->m_fZoom) * nScaleFactor;
    aRectanglePixels.height = twipToPixel(rRectangle.height, priv->m_fZoom) * nScaleFactor;

    aStart.x = aRectanglePixels.y / nTileSizePixelsScaled;
    aStart.y = aRectanglePixels.x / nTileSizePixelsScaled;
    aEnd.x = (aRectanglePixels.y + aRectanglePixels.height + nTileSizePixelsScaled) / nTileSizePixelsScaled;
    aEnd.y = (aRectanglePixels.x + aRectanglePixels.width + nTileSizePixelsScaled) / nTileSizePixelsScaled;
    for (int i = aStart.x; i < aEnd.x; i++)
    {
        for (int j = aStart.y; j < aEnd.y; j++)
        {
            GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
            priv->m_pTileBuffer->setInvalid(i, j, priv->m_fZoom, task, priv->lokThreadPool);
            g_object_unref(task);
        }
    }
}

static gboolean
callback (gpointer pData)
{
    CallbackData* pCallback = static_cast<CallbackData*>(pData);
    LOKDocView* pDocView = LOK_DOC_VIEW (pCallback->m_pDocView);
    LOKDocViewPrivate& priv = getPrivate(pDocView);

    //callback registered before the widget was destroyed.
    //Use existence of lokThreadPool as flag it was torn down
    if (!priv->lokThreadPool)
    {
        delete pCallback;
        return G_SOURCE_REMOVE;
    }

    switch (static_cast<LibreOfficeKitCallbackType>(pCallback->m_nType))
    {
    case LOK_CALLBACK_INVALIDATE_TILES:
    {
        if (pCallback->m_aPayload.compare(0, 5, "EMPTY") != 0) // payload doesn't start with "EMPTY"
        {
            GdkRectangle aRectangle = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
            setTilesInvalid(pDocView, aRectangle);
        }
        else
            priv->m_pTileBuffer->resetAllTiles();

        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
    }
    break;
    case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
    {

        std::stringstream aStream(pCallback->m_aPayload);
        boost::property_tree::ptree aTree;
        boost::property_tree::read_json(aStream, aTree);
        const std::string rRectangle = aTree.get<std::string>("rectangle");
        int nViewId = aTree.get<int>("viewId");

        priv->m_aVisibleCursor = payloadToRectangle(pDocView, rRectangle.c_str());
        priv->m_bCursorOverlayVisible = true;
        if(nViewId == priv->m_nViewId)
        {
            g_signal_emit(pDocView, doc_view_signals[CURSOR_CHANGED], 0,
                      priv->m_aVisibleCursor.x,
                      priv->m_aVisibleCursor.y,
                      priv->m_aVisibleCursor.width,
                      priv->m_aVisibleCursor.height);
        }
        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
    }
    break;
    case LOK_CALLBACK_TEXT_SELECTION:
    {
        priv->m_aTextSelectionRectangles = payloadToRectangles(pDocView, pCallback->m_aPayload.c_str());
        bool bIsTextSelected = !priv->m_aTextSelectionRectangles.empty();
        // In case the selection is empty, then we get no LOK_CALLBACK_TEXT_SELECTION_START/END events.
        if (!bIsTextSelected)
        {
            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));

        g_signal_emit(pDocView, doc_view_signals[TEXT_SELECTION], 0, bIsTextSelected);
        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
    }
    break;
    case LOK_CALLBACK_TEXT_SELECTION_START:
    {
        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_MOUSE_POINTER:
    {
        // We do not want the cursor to get changed in view-only mode
        if (priv->m_bEdit)
        {
            // The gtk docs claim that most css cursors should be supported, however
            // on my system at least this is not true and many cursors are unsupported.
            // In this case pCursor = null, which results in the default cursor
            // being set.
            GdkCursor* pCursor = gdk_cursor_new_from_name(gtk_widget_get_display(GTK_WIDGET(pDocView)),
                                                          pCallback->m_aPayload.c_str());
            gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(pDocView)), pCursor);
        }
    }
    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_GRAPHIC_VIEW_SELECTION:
    {
        std::stringstream aStream(pCallback->m_aPayload);
        boost::property_tree::ptree aTree;
        boost::property_tree::read_json(aStream, aTree);
        int nViewId = aTree.get<int>("viewId");
        int nPart = aTree.get<int>("part");
        const std::string rRectangle = aTree.get<std::string>("selection");
        if (rRectangle != "EMPTY")
            priv->m_aGraphicViewSelections[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
        else
        {
            auto it = priv->m_aGraphicViewSelections.find(nViewId);
            if (it != priv->m_aGraphicViewSelections.end())
                priv->m_aGraphicViewSelections.erase(it);
        }
        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
        break;
    }
    break;
    case LOK_CALLBACK_CELL_CURSOR:
    {
        if (pCallback->m_aPayload != "EMPTY")
            priv->m_aCellCursor = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
        else
            memset(&priv->m_aCellCursor, 0, sizeof(priv->m_aCellCursor));
        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
    }
    break;
    case LOK_CALLBACK_HYPERLINK_CLICKED:
    {
        hyperlinkClicked(pDocView, pCallback->m_aPayload);
    }
    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:
    {
        refreshSize(pDocView);
        g_signal_emit(pDocView, doc_view_signals[SIZE_CHANGED], 0, nullptr);
    }
    break;
    case LOK_CALLBACK_SET_PART:
    {
        setPart(pDocView, pCallback->m_aPayload);
    }
    break;
    case LOK_CALLBACK_SEARCH_RESULT_SELECTION:
    {
        boost::property_tree::ptree aTree;
        std::stringstream aStream(pCallback->m_aPayload);
        boost::property_tree::read_json(aStream, aTree);
        int nCount = aTree.get_child("searchResultSelection").size();
        searchResultCount(pDocView, std::to_string(nCount));
    }
    break;
    case LOK_CALLBACK_UNO_COMMAND_RESULT:
    {
        commandResult(pDocView, pCallback->m_aPayload);
    }
    break;
    case LOK_CALLBACK_CELL_ADDRESS:
    {
        addressChanged(pDocView, pCallback->m_aPayload);
    }
    break;
    case LOK_CALLBACK_CELL_FORMULA:
    {
        formulaChanged(pDocView, pCallback->m_aPayload);
    }
    break;
    case LOK_CALLBACK_ERROR:
    {
        reportError(pDocView, pCallback->m_aPayload);
    }
    break;
    case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
    {
        std::stringstream aStream(pCallback->m_aPayload);
        boost::property_tree::ptree aTree;
        boost::property_tree::read_json(aStream, aTree);
        int nViewId = aTree.get<int>("viewId");
        int nPart = aTree.get<int>("part");
        const std::string rRectangle = aTree.get<std::string>("rectangle");
        priv->m_aViewCursors[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
        break;
    }
    case LOK_CALLBACK_TEXT_VIEW_SELECTION:
    {
        std::stringstream aStream(pCallback->m_aPayload);
        boost::property_tree::ptree aTree;
        boost::property_tree::read_json(aStream, aTree);
        int nViewId = aTree.get<int>("viewId");
        int nPart = aTree.get<int>("part");
        const std::string rSelection = aTree.get<std::string>("selection");
        priv->m_aTextViewSelectionRectangles[nViewId] = ViewRectangles(nPart, payloadToRectangles(pDocView, rSelection.c_str()));
        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
        break;
    }
    case LOK_CALLBACK_VIEW_CURSOR_VISIBLE:
    {
        std::stringstream aStream(pCallback->m_aPayload);
        boost::property_tree::ptree aTree;
        boost::property_tree::read_json(aStream, aTree);
        int nViewId = aTree.get<int>("viewId");
        const std::string rVisible = aTree.get<std::string>("visible");
        priv->m_aViewCursorVisibilities[nViewId] = rVisible == "true";
        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
        break;
    }
    break;
    case LOK_CALLBACK_CELL_VIEW_CURSOR:
    {
        std::stringstream aStream(pCallback->m_aPayload);
        boost::property_tree::ptree aTree;
        boost::property_tree::read_json(aStream, aTree);
        int nViewId = aTree.get<int>("viewId");
        int nPart = aTree.get<int>("part");
        const std::string rRectangle = aTree.get<std::string>("rectangle");
        if (rRectangle != "EMPTY")
            priv->m_aCellViewCursors[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
        else
        {
            auto it = priv->m_aCellViewCursors.find(nViewId);
            if (it != priv->m_aCellViewCursors.end())
                priv->m_aCellViewCursors.erase(it);
        }
        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
        break;
    }
    case LOK_CALLBACK_VIEW_LOCK:
    {
        std::stringstream aStream(pCallback->m_aPayload);
        boost::property_tree::ptree aTree;
        boost::property_tree::read_json(aStream, aTree);
        int nViewId = aTree.get<int>("viewId");
        int nPart = aTree.get<int>("part");
        const std::string rRectangle = aTree.get<std::string>("rectangle");
        if (rRectangle != "EMPTY")
            priv->m_aViewLockRectangles[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
        else
        {
            auto it = priv->m_aViewLockRectangles.find(nViewId);
            if (it != priv->m_aViewLockRectangles.end())
                priv->m_aViewLockRectangles.erase(it);
        }
        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
        break;
    }
    case LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED:
    {
        break;
    }
    case LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED:
    {
        break;
    }
    case LOK_CALLBACK_COMMENT:
        g_signal_emit(pCallback->m_pDocView, doc_view_signals[COMMENT], 0, pCallback->m_aPayload.c_str());
        break;
    case LOK_CALLBACK_RULER_UPDATE:
        g_signal_emit(pCallback->m_pDocView, doc_view_signals[RULER], 0, pCallback->m_aPayload.c_str());
        break;
    case LOK_CALLBACK_VERTICAL_RULER_UPDATE:
        g_signal_emit(pCallback->m_pDocView, doc_view_signals[RULER], 0, pCallback->m_aPayload.c_str());
        break;
    case LOK_CALLBACK_WINDOW:
        g_signal_emit(pCallback->m_pDocView, doc_view_signals[WINDOW], 0, pCallback->m_aPayload.c_str());
        break;
    case LOK_CALLBACK_INVALIDATE_HEADER:
        g_signal_emit(pCallback->m_pDocView, doc_view_signals[INVALIDATE_HEADER], 0, pCallback->m_aPayload.c_str());
        break;
    case LOK_CALLBACK_REFERENCE_MARKS:
    {
        std::stringstream aStream(pCallback->m_aPayload);
        boost::property_tree::ptree aTree;
        boost::property_tree::read_json(aStream, aTree);

        priv->m_aReferenceMarks.clear();

        for(const auto& rMark : aTree.get_child("marks"))
        {
            sal_uInt32 nColor = std::stoi(rMark.second.get<std::string>("color"), nullptr, 16);
            std::string sRect = rMark.second.get<std::string>("rectangle");
            sal_uInt32 nPart = std::stoi(rMark.second.get<std::string>("part"));

            GdkRectangle aRect = payloadToRectangle(pDocView, sRect.c_str());
            priv->m_aReferenceMarks.push_back(std::pair<ViewRectangle, sal_uInt32>(ViewRectangle(nPart, aRect), nColor));
        }

        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
        break;
    }

    case LOK_CALLBACK_CONTENT_CONTROL:
    {
        std::stringstream aPayloadStream(pCallback->m_aPayload);
        boost::property_tree::ptree aTree;
        boost::property_tree::read_json(aPayloadStream, aTree);
        auto aAction = aTree.get<std::string>("action");
        if (aAction == "show")
        {
            auto aRectangles = aTree.get<std::string>("rectangles");
            priv->m_aContentControlRectangles = payloadToRectangles(pDocView, aRectangles.c_str());

            auto it = aTree.find("alias");
            if (it == aTree.not_found())
            {
                priv->m_aContentControlAlias.clear();
            }
            else
            {
                priv->m_aContentControlAlias = it->second.get_value<std::string>();
            }
        }
        else if (aAction == "hide")
        {
            priv->m_aContentControlRectangles.clear();
            priv->m_aContentControlAlias.clear();
        }
        else if (aAction == "change-picture")
        {
            GtkWidget* pDialog = gtk_file_chooser_dialog_new(
                "Open File", GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))),
                GTK_FILE_CHOOSER_ACTION_OPEN, "Cancel", GTK_RESPONSE_CANCEL, "Open",
                GTK_RESPONSE_ACCEPT, nullptr);
            gint nRet = gtk_dialog_run(GTK_DIALOG(pDialog));
            if (nRet == GTK_RESPONSE_ACCEPT)
            {
                GtkFileChooser* pChooser = GTK_FILE_CHOOSER(pDialog);
                char* pFilename = gtk_file_chooser_get_uri(pChooser);
                boost::property_tree::ptree aValues;
                aValues.put("type""picture");
                aValues.put("changed", pFilename);
                std::stringstream aStream;
                boost::property_tree::write_json(aStream, aValues);
                std::string aJson = aStream.str();
                lok_doc_view_send_content_control_event(pDocView, aJson.c_str());

                g_free(pFilename);
            }
            gtk_widget_destroy(pDialog);
        }
        g_signal_emit(pCallback->m_pDocView, doc_view_signals[CONTENT_CONTROL], 0,
                      pCallback->m_aPayload.c_str());
        gtk_widget_queue_draw(GTK_WIDGET(pDocView));
    }
    break;

    case LOK_CALLBACK_STATUS_INDICATOR_START:
    case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
    case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
    case LOK_CALLBACK_DOCUMENT_PASSWORD:
    case LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY:
    case LOK_CALLBACK_VALIDITY_LIST_BUTTON:
    case LOK_CALLBACK_VALIDITY_INPUT_HELP:
    case LOK_CALLBACK_SIGNATURE_STATUS:
    case LOK_CALLBACK_CONTEXT_MENU:
    case LOK_CALLBACK_PROFILE_FRAME:
    case LOK_CALLBACK_CLIPBOARD_CHANGED:
    case LOK_CALLBACK_CONTEXT_CHANGED:
    case LOK_CALLBACK_CELL_SELECTION_AREA:
    case LOK_CALLBACK_CELL_AUTO_FILL_AREA:
    case LOK_CALLBACK_TABLE_SELECTED:
    case LOK_CALLBACK_JSDIALOG:
    case LOK_CALLBACK_CALC_FUNCTION_LIST:
    case LOK_CALLBACK_TAB_STOP_LIST:
    case LOK_CALLBACK_FORM_FIELD_BUTTON:
    case LOK_CALLBACK_INVALIDATE_SHEET_GEOMETRY:
    case LOK_CALLBACK_DOCUMENT_BACKGROUND_COLOR:
    case LOK_COMMAND_BLOCKED:
    case LOK_CALLBACK_SC_FOLLOW_JUMP:
    case LOK_CALLBACK_PRINT_RANGES:
    case LOK_CALLBACK_FONTS_MISSING:
    case LOK_CALLBACK_MEDIA_SHAPE:
    case LOK_CALLBACK_EXPORT_FILE:
    case LOK_CALLBACK_VIEW_RENDER_STATE:
    case LOK_CALLBACK_APPLICATION_BACKGROUND_COLOR:
    case LOK_CALLBACK_A11Y_FOCUS_CHANGED:
    case LOK_CALLBACK_A11Y_CARET_CHANGED:
    case LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED:
    case LOK_CALLBACK_A11Y_FOCUSED_CELL_CHANGED:
    case LOK_CALLBACK_COLOR_PALETTES:
    case LOK_CALLBACK_DOCUMENT_PASSWORD_RESET:
    case LOK_CALLBACK_A11Y_EDITING_IN_SELECTION_STATE:
    case LOK_CALLBACK_A11Y_SELECTION_CHANGED:
    case LOK_CALLBACK_CORE_LOG:
    case LOK_CALLBACK_TOOLTIP:
    case LOK_CALLBACK_SHAPE_INNER_TEXT:
    {
        // TODO: Implement me
        break;
    }
    }
    delete pCallback;

    return G_SOURCE_REMOVE;
}

static void callbackWorker (int nType, const char* pPayload, void* pData)
{
    LOKDocView* pDocView = LOK_DOC_VIEW (pData);

    CallbackData* pCallback = new CallbackData(nType, pPayload ? pPayload : "(nil)", pDocView);
    LOKDocViewPrivate& priv = getPrivate(pDocView);
    std::stringstream ss;
    ss << "callbackWorker, view #" << priv->m_nViewId << ": " << lokCallbackTypeToString(nType) << ", '" << (pPayload ? pPayload : "(nil)") << "'";
    g_info("%s", ss.str().c_str());
    gdk_threads_add_idle(callback, pCallback);
}

static void
renderHandle(LOKDocView* pDocView,
             cairo_t* pCairo,
             const GdkRectangle& rCursor,
             cairo_surface_t* pHandle,
             GdkRectangle& rRectangle)
{
    LOKDocViewPrivate& priv = getPrivate(pDocView);
    gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
    GdkPoint aCursorBottom;
    int nHandleWidth, nHandleHeight;
    double fHandleScale;

    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, 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, 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_scale(pCairo, 1.0 / nScaleFactor, 1.0 / nScaleFactor);
    cairo_translate(pCairo, aCursorBottom.x * nScaleFactor, aCursorBottom.y * nScaleFactor);
    cairo_scale(pCairo, fHandleScale * nScaleFactor, fHandleScale * nScaleFactor);
    cairo_set_source_surface(pCairo, pHandle, 0, 0);
    cairo_paint(pCairo);
    cairo_restore (pCairo);

    rRectangle.x = aCursorBottom.x;
    rRectangle.y = aCursorBottom.y;
    rRectangle.width = nHandleWidth * fHandleScale;
    rRectangle.height = nHandleHeight * fHandleScale;
}

/// Renders handles around an rSelection rectangle on pCairo.
static void
renderGraphicHandle(LOKDocView* pDocView,
                    cairo_t* pCairo,
                    const GdkRectangle& rSelection,
                    const GdkRGBA& rColor)
{
    LOKDocViewPrivate& priv = getPrivate(pDocView);
    int nHandleWidth = 9, nHandleHeight = 9;
    GdkRectangle aSelection;

    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;

        switch (i)
        {
        case 0: // top-left
            break;
        case 1: // top-middle
            x += aSelection.width / 2;
            break;
        case 2: // top-right
            x += aSelection.width;
            break;
        case 3: // middle-left
            y += aSelection.height / 2;
            break;
        case 4: // middle-right
            x += aSelection.width;
            y += aSelection.height / 2;
            break;
        case 5: // bottom-left
            y += aSelection.height;
            break;
        case 6: // bottom-middle
            x += aSelection.width / 2;
            y += aSelection.height;
            break;
        case 7: // bottom-right
            x += aSelection.width;
            y += aSelection.height;
            break;
        }

        // Center the handle.
        x -= nHandleWidth / 2;
        y -= nHandleHeight / 2;

        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_set_source_rgb(pCairo, rColor.red, rColor.green, rColor.blue);
        cairo_rectangle(pCairo, x, y, nHandleWidth, nHandleHeight);
        cairo_fill(pCairo);
    }
}

/// Finishes the paint tile operation and returns the result, if any
static gpointer
paintTileFinish(LOKDocView* pDocView, GAsyncResult* res, GError **error)
{
    GTask* task = G_TASK(res);

    g_return_val_if_fail(LOK_IS_DOC_VIEW(pDocView), nullptr);
    g_return_val_if_fail(g_task_is_valid(res, pDocView), nullptr);
    g_return_val_if_fail(error == nullptr || *error == nullptr, nullptr);

    return g_task_propagate_pointer(task, error);
}

/// Callback called in the main UI thread when paintTileInThread in LOK thread has finished
static void
paintTileCallback(GObject* sourceObject, GAsyncResult* res, gpointer userData)
{
    LOKDocView* pDocView = LOK_DOC_VIEW(sourceObject);
    LOKDocViewPrivate& priv = getPrivate(pDocView);
    LOEvent* pLOEvent = static_cast<LOEvent*>(userData);
    std::unique_ptr<TileBuffer>& buffer = priv->m_pTileBuffer;
    GError* error;

    error = nullptr;
    cairo_surface_t* pSurface = static_cast<cairo_surface_t*>(paintTileFinish(pDocView, res, &error));
    if (error != nullptr)
    {
        if (error->domain == LOK_TILEBUFFER_ERROR &&
            error->code == LOK_TILEBUFFER_CHANGED)
            g_info("Skipping paint tile request because corresponding"
                   "tile buffer has been destroyed");
        else
            g_warning("Unable to get painted GdkPixbuf: %s", error->message);
        g_error_free(error);
        return;
    }

    buffer->setTile(pLOEvent->m_nPaintTileX, pLOEvent->m_nPaintTileY, pSurface);
    gdk_threads_add_idle(queueDraw, GTK_WIDGET(pDocView));

    cairo_surface_destroy(pSurface);
}


static bool
renderDocument(LOKDocView* pDocView, cairo_t* pCairo)
{
    LOKDocViewPrivate& priv = getPrivate(pDocView);
    GdkRectangle aVisibleArea;
    gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
    gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
    long nDocumentWidthPixels = twipToPixel(priv->m_nDocumentWidthTwips, priv->m_fZoom) * nScaleFactor;
    long nDocumentHeightPixels = twipToPixel(priv->m_nDocumentHeightTwips, priv->m_fZoom) * nScaleFactor;
    // Total number of rows / columns in this document.
    guint nRows = ceil(static_cast<double>(nDocumentHeightPixels) / nTileSizePixelsScaled);
    guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixelsScaled);

    cairo_save (pCairo);
    cairo_scale (pCairo, 1.0/nScaleFactor, 1.0/nScaleFactor);
    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)
    {
        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 * nTileSizePixelsScaled;
            else
                aTileRectanglePixels.width = nTileSizePixelsScaled;
            if (nRow == nRows - 1)
                aTileRectanglePixels.height = nDocumentHeightPixels - nRow * nTileSizePixelsScaled;
            else
                aTileRectanglePixels.height = nTileSizePixelsScaled;

            // 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(nTileSizePixelsScaled, priv->m_fZoom) * nColumn;
            aTileRectangleTwips.y = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom) * nRow;
            aTileRectangleTwips.width = pixelToTwip(aTileRectanglePixels.width, priv->m_fZoom);
            aTileRectangleTwips.height = pixelToTwip(aTileRectanglePixels.height, priv->m_fZoom);

            if (!gdk_rectangle_intersect(&aVisibleArea, &aTileRectangleTwips, nullptr))
                bPaint = false;

            if (bPaint)
            {
                LOEvent* pLOEvent = new LOEvent(LOK_PAINT_TILE);
                pLOEvent->m_nPaintTileX = nRow;
                pLOEvent->m_nPaintTileY = nColumn;
                pLOEvent->m_fPaintTileZoom = priv->m_fZoom;
                pLOEvent->m_pTileBuffer = &*priv->m_pTileBuffer;
                GTask* task = g_task_new(pDocView, nullptr, paintTileCallback, pLOEvent);
                g_task_set_task_data(task, pLOEvent, LOEvent::destroy);

                Tile& currentTile = priv->m_pTileBuffer->getTile(nRow, nColumn, task, priv->lokThreadPool);
                cairo_surface_t* pSurface = currentTile.getBuffer();
                cairo_set_source_surface(pCairo, pSurface,
                                             twipToPixel(aTileRectangleTwips.x, priv->m_fZoom),
                                             twipToPixel(aTileRectangleTwips.y, priv->m_fZoom));
                cairo_paint(pCairo);
                g_object_unref(task);
            }
        }
    }

    cairo_restore (pCairo);
    return false;
}

static const GdkRGBA& getDarkColor(int nViewId, LOKDocViewPrivate& priv)
{
    static std::map<int, GdkRGBA> aColorMap;
    auto it = aColorMap.find(nViewId);
    if (it != aColorMap.end())
        return it->second;

    if (priv->m_eDocumentType == LOK_DOCTYPE_TEXT)
    {
        char* pValues = priv->m_pDocument->pClass->getCommandValues(priv->m_pDocument, ".uno:TrackedChangeAuthors");
        std::stringstream aInfo;
        aInfo << "lok::Document::getCommandValues('.uno:TrackedChangeAuthors') returned '" << pValues << "'" << std::endl;
        g_info("%s", aInfo.str().c_str());

        std::stringstream aStream(pValues);
        boost::property_tree::ptree aTree;
        boost::property_tree::read_json(aStream, aTree);
        for (const auto& rValue : aTree.get_child("authors"))
        {
            const std::string rName = rValue.second.get<std::string>("name");
            guint32 nColor = rValue.second.get<guint32>("color");
            GdkRGBA aColor{static_cast<double>(static_cast<guint8>(nColor>>16))/255, static_cast<double>(static_cast<guint8>(static_cast<guint16>(nColor) >> 8))/255, static_cast<double>(static_cast<guint8>(nColor))/255, 0};
            auto itAuthorViews = g_aAuthorViews.find(rName);
            if (itAuthorViews != g_aAuthorViews.end())
                aColorMap[itAuthorViews->second] = aColor;
        }
    }
    else
    {
        // Based on tools/color.hxx, COL_AUTHOR1_DARK..COL_AUTHOR9_DARK.
        static std::vector<GdkRGBA> aColors =
        {
            {(double(198))/255, (double(146))/255, (double(0))/255, 0},
            {(double(6))/255, (double(70))/255, (double(162))/255, 0},
            {(double(87))/255, (double(157))/255, (double(28))/255, 0},
            {(double(105))/255, (double(43))/255, (double(157))/255, 0},
            {(double(197))/255, (double(0))/255, (double(11))/255, 0},
            {(double(0))/255, (double(128))/255, (double(128))/255, 0},
            {(double(140))/255, (double(132))/255, (double(0))/255, 0},
            {(double(43))/255, (double(85))/255, (double(107))/255, 0},
            {(double(209))/255, (double(118))/255, (double(0))/255, 0},
        };
        static int nColorCounter = 0;
        GdkRGBA aColor = aColors[nColorCounter++ % aColors.size()];
        aColorMap[nViewId] = aColor;
    }
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=94 H=89 G=91

¤ Dauer der Verarbeitung: 0.48 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.