office-gobmx/libreofficekit/source/gtk/lokdocview.cxx
Tamás Zolnai 83b7bfc0ef sw lok: View jumps to cursor position even if it is moved by an other view.
Scrolling is done twice. Once in SwCursorShell::UpdateCursor() by
SCROLLWIN flag. Here we can check the actual viewid and avoid scrolling
if the cursor is move by an other user.
The second instance in the LO online code, for it we need to pass the
viewid identifying the view which moved the cursor.

Change-Id: I033274f88ce41acbb632e2aeb0d986ab11cd2d52
Reviewed-on: https://gerrit.libreoffice.org/52220
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Tamás Zolnai <tamas.zolnai@collabora.com>
2018-04-07 08:05:04 +02:00

3814 lines
140 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* 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 <vector>
#include <string>
#include <sstream>
#include <iostream>
#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;
/// 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, const std::vector<GdkRectangle>& rRectangles = std::vector<GdkRectangle>())
: m_nPart(nPart),
m_aRectangles(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;
gboolean m_bIsLoading;
gboolean m_bInit; // initializeForRendering() has been called
gboolean m_bCanZoomIn;
gboolean m_bCanZoomOut;
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.
gboolean 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).
gboolean m_bCursorOverlayVisible;
/// Cursor is visible or hidden (e.g. for graphic selection).
gboolean m_bCursorVisible;
/// Visibility of view selections. The current view can only see / them,
/// can't modify them. Key is the view id.
std::map<int, bool> 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 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;
gboolean m_bInDragGraphicSelection;
/// @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.
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.
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.
gboolean 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.
gboolean 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_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(0),
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);
}
};
/// 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,
PASSWORD_REQUIRED,
COMMENT,
RULER,
WINDOW,
INVALIDATE_HEADER,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_LO_PATH,
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);
SAL_DLLPUBLIC_EXPORT GType lok_doc_view_get_type();
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#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;
}
/// 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, 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);
gchar** ppCoordinate = ppCoordinates;
if (!*ppCoordinate)
return;
rWidth = atoi(*ppCoordinate);
++ppCoordinate;
if (!*ppCoordinate)
return;
rHeight = atoi(*ppCoordinate);
g_strfreev(ppCoordinates);
}
/// Returns the string representation of a LibreOfficeKitCallbackType enumeration element.
static const char*
callbackTypeToString (int 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_VIEW_CURSOR_VISIBLE:
return "LOK_CALLBACK_VIEW_CURSOR_VISIBLE";
case LOK_CALLBACK_GRAPHIC_SELECTION:
return "LOK_CALLBACK_GRAPHIC_SELECTION";
case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
return "LOK_CALLBACK_GRAPHIC_VIEW_SELECTION";
case LOK_CALLBACK_CELL_CURSOR:
return "LOK_CALLBACK_CELL_CURSOR";
case LOK_CALLBACK_HYPERLINK_CLICKED:
return "LOK_CALLBACK_HYPERLINK_CLICKED";
case LOK_CALLBACK_MOUSE_POINTER:
return "LOK_CALLBACK_MOUSE_POINTER";
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";
case LOK_CALLBACK_SEARCH_RESULT_SELECTION:
return "LOK_CALLBACK_SEARCH_RESULT_SELECTION";
case LOK_CALLBACK_DOCUMENT_PASSWORD:
return "LOK_CALLBACK_DOCUMENT_PASSWORD";
case LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY:
return "LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY";
case LOK_CALLBACK_CONTEXT_MENU:
return "LOK_CALLBACK_CONTEXT_MENU";
case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
return "LOK_CALLBACK_INVALIDATE_VIEW_CURSOR";
case LOK_CALLBACK_TEXT_VIEW_SELECTION:
return "LOK_CALLBACK_TEXT_VIEW_SELECTION";
case LOK_CALLBACK_CELL_VIEW_CURSOR:
return "LOK_CALLBACK_CELL_VIEW_CURSOR";
case LOK_CALLBACK_CELL_ADDRESS:
return "LOK_CALLBACK_CELL_ADDRESS";
case LOK_CALLBACK_CELL_FORMULA:
return "LOK_CALLBACK_CELL_FORMULA";
case LOK_CALLBACK_UNO_COMMAND_RESULT:
return "LOK_CALLBACK_UNO_COMMAND_RESULT";
case LOK_CALLBACK_ERROR:
return "LOK_CALLBACK_ERROR";
case LOK_CALLBACK_VIEW_LOCK:
return "LOK_CALLBACK_VIEW_LOCK";
case LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED:
return "LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED";
case LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED:
return "LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED";
case LOK_CALLBACK_INVALIDATE_HEADER:
return "LOK_CALLBACK_INVALIDATE_HEADER";
case LOK_CALLBACK_COMMENT:
return "LOK_CALLBACK_COMMENT";
case LOK_CALLBACK_RULER_UPDATE:
return "LOK_CALLBACK_RULER_UPDATE";
case LOK_CALLBACK_WINDOW:
return "LOK_CALLBACK_WINDOW";
}
g_assert(false);
return nullptr;
}
static void
LOKPostCommand (LOKDocView* pDocView,
const gchar* pCommand,
const gchar* pArguments,
gboolean 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)
{
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;
}
return FALSE;
}
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));
std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
std::stringstream ss;
ss << "lok::Document::setView(" << priv->m_nViewId << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
if (priv->m_nTileSizeTwips)
{
ss.str(std::string());
ss << "lok::Document::setClientZoom(" << nTileSizePixels << ", " << nTileSizePixels << ", " << priv->m_nTileSizeTwips << ", " << priv->m_nTileSizeTwips << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->setClientZoom(priv->m_pDocument,
nTileSizePixels,
nTileSizePixels,
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 = com::sun::star::awt::Key::BACKSPACE;
break;
case GDK_KEY_Delete:
nKeyCode = com::sun::star::awt::Key::DELETE;
break;
case GDK_KEY_Return:
case GDK_KEY_KP_Enter:
nKeyCode = com::sun::star::awt::Key::RETURN;
break;
case GDK_KEY_Escape:
nKeyCode = com::sun::star::awt::Key::ESCAPE;
break;
case GDK_KEY_Tab:
nKeyCode = com::sun::star::awt::Key::TAB;
break;
case GDK_KEY_Down:
nKeyCode = com::sun::star::awt::Key::DOWN;
break;
case GDK_KEY_Up:
nKeyCode = com::sun::star::awt::Key::UP;
break;
case GDK_KEY_Left:
nKeyCode = com::sun::star::awt::Key::LEFT;
break;
case GDK_KEY_Right:
nKeyCode = com::sun::star::awt::Key::RIGHT;
break;
case GDK_KEY_Page_Down:
nKeyCode = com::sun::star::awt::Key::PAGEDOWN;
break;
case GDK_KEY_Page_Up:
nKeyCode = com::sun::star::awt::Key::PAGEUP;
break;
case GDK_KEY_Insert:
nKeyCode = com::sun::star::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 = com::sun::star::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 std::pair<std::string, boost::property_tree::ptree>& 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;
/// 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());
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_pDocument->pClass->getDocumentSize(priv->m_pDocument, &priv->m_nDocumentWidthTwips, &priv->m_nDocumentHeightTwips);
priv->m_nParts = priv->m_pDocument->pClass->getParts(priv->m_pDocument);
aGuard.unlock();
priv->m_nTimeoutId = g_timeout_add(600, handleTimeout, pLOKDocView);
float zoom = priv->m_fZoom;
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) / nTileSizePixels);
priv->m_pTileBuffer = std::unique_ptr<TileBuffer>(new TileBuffer(nColumns));
gtk_widget_set_size_request(GTK_WIDGET(pLOKDocView),
nDocumentWidthPixels,
nDocumentHeightPixels);
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;
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);
gboolean 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;
SAL_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;
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'", callbackTypeToString(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)
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;
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)
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 const 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;
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++)
{
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 (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;
std::cerr << nViewId;
std::cerr << priv->m_nViewId;
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());
gboolean 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:
{
if (!pCallback->m_aPayload.empty())
payloadToSize(pCallback->m_aPayload.c_str(), priv->m_nDocumentWidthTwips, priv->m_nDocumentHeightTwips);
else
priv->m_pDocument->pClass->getDocumentSize(priv->m_pDocument, &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));
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_CONTEXT_MENU:
{
// TODO: Implement me
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_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;
default:
g_assert(false);
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 << ": " << callbackTypeToString(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);
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_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);
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;
int index = pLOEvent->m_nPaintTileX * buffer->m_nWidth + pLOEvent->m_nPaintTileY;
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->m_mTiles[index].setSurface(pSurface);
buffer->m_mTiles[index].valid = true;
gdk_threads_add_idle(queueDraw, GTK_WIDGET(pDocView));
cairo_surface_destroy(pSurface);
}
static gboolean
renderDocument(LOKDocView* pDocView, cairo_t* pCairo)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
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(static_cast<double>(nDocumentHeightPixels) / nTileSizePixels);
guint nColumns = ceil(static_cast<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)
{
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, 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);
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);
}
}
}
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;
}
assert(aColorMap.find(nViewId) != aColorMap.end());
return aColorMap[nViewId];
}
static gboolean
renderOverlay(LOKDocView* pDocView, cairo_t* pCairo)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
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;
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);
}
// View cursors: they do not blink and are colored.
if (priv->m_bEdit && !priv->m_aViewCursors.empty())
{
for (auto& rPair : priv->m_aViewCursors)
{
auto itVisibility = priv->m_aViewCursorVisibilities.find(rPair.first);
if (itVisibility != priv->m_aViewCursorVisibilities.end() && !itVisibility->second)
continue;
// Show view cursors when in Writer or when the part matches.
if (rPair.second.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
continue;
GdkRectangle& rCursor = rPair.second.m_aRectangle;
if (rCursor.width < 30)
// Set a minimal width if it would be 0.
rCursor.width = 30;
const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
cairo_rectangle(pCairo,
twipToPixel(rCursor.x, priv->m_fZoom),
twipToPixel(rCursor.y, priv->m_fZoom),
twipToPixel(rCursor.width, priv->m_fZoom),
twipToPixel(rCursor.height, priv->m_fZoom));
cairo_fill(pCairo);
}
}
if (priv->m_bEdit && priv->m_bCursorVisible && !isEmptyRectangle(priv->m_aVisibleCursor) && priv->m_aTextSelectionRectangles.empty())
{
// Have a cursor, but no selection: we need the middle handle.
gchar* handleMiddlePath = g_strconcat (priv->m_aLOPath.c_str(), CURSOR_HANDLE_DIR, "handle_image_middle.png", nullptr);
if (!priv->m_pHandleMiddle)
{
priv->m_pHandleMiddle = cairo_image_surface_create_from_png(handleMiddlePath);
assert(cairo_surface_status(priv->m_pHandleMiddle) == CAIRO_STATUS_SUCCESS);
}
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.c_str(), CURSOR_HANDLE_DIR, "handle_image_start.png", nullptr);
if (!priv->m_pHandleStart)
{
priv->m_pHandleStart = cairo_image_surface_create_from_png(handleStartPath);
assert(cairo_surface_status(priv->m_pHandleStart) == CAIRO_STATUS_SUCCESS);
}
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.c_str(), CURSOR_HANDLE_DIR, "handle_image_end.png", nullptr);
if (!priv->m_pHandleEnd)
{
priv->m_pHandleEnd = cairo_image_surface_create_from_png(handleEndPath);
assert(cairo_surface_status(priv->m_pHandleEnd) == CAIRO_STATUS_SUCCESS);
}
renderHandle(pDocView, pCairo, priv->m_aTextSelectionEnd, priv->m_pHandleEnd, priv->m_aHandleEndRect);
g_free (handleEndPath);
}
}
// Selections of other views.
for (auto& rPair : priv->m_aTextViewSelectionRectangles)
{
if (rPair.second.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
continue;
for (GdkRectangle& rRectangle : rPair.second.m_aRectangles)
{
const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
// 75% transparency.
cairo_set_source_rgba(pCairo, rDark.red, rDark.green, rDark.blue, 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);
}
}
if (!isEmptyRectangle(priv->m_aGraphicSelection))
{
GdkRGBA const aBlack{0, 0, 0, 0};
renderGraphicHandle(pDocView, pCairo, priv->m_aGraphicSelection, aBlack);
}
// Graphic selections of other views.
for (auto& rPair : priv->m_aGraphicViewSelections)
{
const ViewRectangle& rRectangle = rPair.second;
if (rRectangle.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
continue;
const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
renderGraphicHandle(pDocView, pCairo, rRectangle.m_aRectangle, rDark);
}
// Draw the cell cursor.
if (!isEmptyRectangle(priv->m_aCellCursor))
{
cairo_set_source_rgb(pCairo, 0, 0, 0);
cairo_rectangle(pCairo,
twipToPixel(priv->m_aCellCursor.x, priv->m_fZoom),
twipToPixel(priv->m_aCellCursor.y, priv->m_fZoom),
twipToPixel(priv->m_aCellCursor.width, priv->m_fZoom),
twipToPixel(priv->m_aCellCursor.height, priv->m_fZoom));
cairo_set_line_width(pCairo, 2.0);
cairo_stroke(pCairo);
}
// Cell view cursors: they are colored.
for (auto& rPair : priv->m_aCellViewCursors)
{
const ViewRectangle& rCursor = rPair.second;
if (rCursor.m_nPart != priv->m_nPartId)
continue;
const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
cairo_rectangle(pCairo,
twipToPixel(rCursor.m_aRectangle.x, priv->m_fZoom),
twipToPixel(rCursor.m_aRectangle.y, priv->m_fZoom),
twipToPixel(rCursor.m_aRectangle.width, priv->m_fZoom),
twipToPixel(rCursor.m_aRectangle.height, priv->m_fZoom));
cairo_set_line_width(pCairo, 2.0);
cairo_stroke(pCairo);
}
// View locks: they are colored.
for (auto& rPair : priv->m_aViewLockRectangles)
{
const ViewRectangle& rRectangle = rPair.second;
if (rRectangle.m_nPart != priv->m_nPartId)
continue;
// Draw a rectangle.
const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
cairo_rectangle(pCairo,
twipToPixel(rRectangle.m_aRectangle.x, priv->m_fZoom),
twipToPixel(rRectangle.m_aRectangle.y, priv->m_fZoom),
twipToPixel(rRectangle.m_aRectangle.width, priv->m_fZoom),
twipToPixel(rRectangle.m_aRectangle.height, priv->m_fZoom));
cairo_set_line_width(pCairo, 2.0);
cairo_stroke(pCairo);
// And a lock.
cairo_rectangle(pCairo,
twipToPixel(rRectangle.m_aRectangle.x + rRectangle.m_aRectangle.width, priv->m_fZoom) - 25,
twipToPixel(rRectangle.m_aRectangle.y + rRectangle.m_aRectangle.height, priv->m_fZoom) - 15,
20,
10);
cairo_fill(pCairo);
cairo_arc(pCairo,
twipToPixel(rRectangle.m_aRectangle.x + rRectangle.m_aRectangle.width, priv->m_fZoom) - 15,
twipToPixel(rRectangle.m_aRectangle.y + rRectangle.m_aRectangle.height, priv->m_fZoom) - 15,
5,
180.0 * (M_PI/180.0),
360.0 * (M_PI/180.0));
cairo_stroke(pCairo);
}
return FALSE;
}
static gboolean
lok_doc_view_signal_button(GtkWidget* pWidget, GdkEventButton* pEvent)
{
LOKDocView* pDocView = LOK_DOC_VIEW (pWidget);
LOKDocViewPrivate& priv = getPrivate(pDocView);
GError* error = nullptr;
g_info("LOKDocView_Impl::signalButton: %d, %d (in twips: %d, %d)",
static_cast<int>(pEvent->x), static_cast<int>(pEvent->y),
static_cast<int>(pixelToTwip(pEvent->x, priv->m_fZoom)),
static_cast<int>(pixelToTwip(pEvent->y, priv->m_fZoom)));
gtk_widget_grab_focus(GTK_WIDGET(pDocView));
switch (pEvent->type)
{
case GDK_BUTTON_PRESS:
{
GdkRectangle aClick;
aClick.x = pEvent->x;
aClick.y = pEvent->y;
aClick.width = 1;
aClick.height = 1;
if (handleTextSelectionOnButtonPress(aClick, pDocView))
return FALSE;
if (handleGraphicSelectionOnButtonPress(aClick, pDocView))
return FALSE;
int nCount = 1;
if ((pEvent->time - priv->m_nLastButtonPressTime) < 250)
nCount++;
priv->m_nLastButtonPressTime = pEvent->time;
GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEBUTTONDOWN;
pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
pLOEvent->m_nPostMouseEventCount = nCount;
switch (pEvent->button)
{
case 1:
pLOEvent->m_nPostMouseEventButton = MOUSE_LEFT;
break;
case 2:
pLOEvent->m_nPostMouseEventButton = MOUSE_MIDDLE;
break;
case 3:
pLOEvent->m_nPostMouseEventButton = MOUSE_RIGHT;
break;
}
pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
priv->m_nLastButtonPressed = pLOEvent->m_nPostMouseEventButton;
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_MOUSE_EVENT: %s", error->message);
g_clear_error(&error);
}
g_object_unref(task);
break;
}
case GDK_BUTTON_RELEASE:
{
if (handleTextSelectionOnButtonRelease(pDocView))
return FALSE;
if (handleGraphicSelectionOnButtonRelease(pDocView, pEvent))
return FALSE;
int nCount = 1;
if ((pEvent->time - priv->m_nLastButtonReleaseTime) < 250)
nCount++;
priv->m_nLastButtonReleaseTime = pEvent->time;
GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEBUTTONUP;
pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
pLOEvent->m_nPostMouseEventCount = nCount;
switch (pEvent->button)
{
case 1:
pLOEvent->m_nPostMouseEventButton = MOUSE_LEFT;
break;
case 2:
pLOEvent->m_nPostMouseEventButton = MOUSE_MIDDLE;
break;
case 3:
pLOEvent->m_nPostMouseEventButton = MOUSE_RIGHT;
break;
}
pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
priv->m_nLastButtonPressed = pLOEvent->m_nPostMouseEventButton;
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_MOUSE_EVENT: %s", error->message);
g_clear_error(&error);
}
g_object_unref(task);
break;
}
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 = getPrivate(pDocView);
GdkPoint aPoint;
GError* error = nullptr;
std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
std::stringstream ss;
ss << "lok::Document::setView(" << priv->m_nViewId << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
if (priv->m_bInDragMiddleHandle)
{
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;
}
if (priv->m_bInDragStartHandle)
{
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;
}
if (priv->m_bInDragEndHandle)
{
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;
}
aGuard.unlock();
for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
{
if (priv->m_bInDragGraphicHandles[i])
{
g_info("lcl_signalMotion: dragging the graphic handle #%d", i);
return FALSE;
}
}
if (priv->m_bInDragGraphicSelection)
{
g_info("lcl_signalMotion: dragging the graphic selection");
return FALSE;
}
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, nullptr))
{
g_info("lcl_signalMotion: start of drag graphic selection");
priv->m_bInDragGraphicSelection = 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(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 FALSE;
}
// Otherwise a mouse move, as on the desktop.
GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEMOVE;
pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
pLOEvent->m_nPostMouseEventCount = 1;
pLOEvent->m_nPostMouseEventButton = priv->m_nLastButtonPressed;
pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
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_MOUSEEVENT_MOUSEMOVE: %s", error->message);
g_clear_error(&error);
}
g_object_unref(task);
return FALSE;
}
static void
setGraphicSelectionInThread(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));
std::lock_guard<std::mutex> aGuard(g_aLOKMutex);
std::stringstream ss;
ss << "lok::Document::setView(" << priv->m_nViewId << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
ss.str(std::string());
ss << "lok::Document::setGraphicSelection(" << pLOEvent->m_nSetGraphicSelectionType;
ss << ", " << pLOEvent->m_nSetGraphicSelectionX;
ss << ", " << pLOEvent->m_nSetGraphicSelectionY << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->setGraphicSelection(priv->m_pDocument,
pLOEvent->m_nSetGraphicSelectionType,
pLOEvent->m_nSetGraphicSelectionX,
pLOEvent->m_nSetGraphicSelectionY);
}
static void
setClientZoomInThread(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));
std::lock_guard<std::mutex> aGuard(g_aLOKMutex);
priv->m_pDocument->pClass->setClientZoom(priv->m_pDocument,
pLOEvent->m_nTilePixelWidth,
pLOEvent->m_nTilePixelHeight,
pLOEvent->m_nTileTwipWidth,
pLOEvent->m_nTileTwipHeight);
}
static void
postMouseEventInThread(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));
std::lock_guard<std::mutex> aGuard(g_aLOKMutex);
std::stringstream ss;
ss << "lok::Document::setView(" << priv->m_nViewId << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
ss.str(std::string());
ss << "lok::Document::postMouseEvent(" << pLOEvent->m_nPostMouseEventType;
ss << ", " << pLOEvent->m_nPostMouseEventX;
ss << ", " << pLOEvent->m_nPostMouseEventY;
ss << ", " << pLOEvent->m_nPostMouseEventCount;
ss << ", " << pLOEvent->m_nPostMouseEventButton;
ss << ", " << pLOEvent->m_nPostMouseEventModifier << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->postMouseEvent(priv->m_pDocument,
pLOEvent->m_nPostMouseEventType,
pLOEvent->m_nPostMouseEventX,
pLOEvent->m_nPostMouseEventY,
pLOEvent->m_nPostMouseEventCount,
pLOEvent->m_nPostMouseEventButton,
pLOEvent->m_nPostMouseEventModifier);
}
static void
openDocumentInThread (gpointer data)
{
GTask* task = G_TASK(data);
LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
LOKDocViewPrivate& priv = getPrivate(pDocView);
std::lock_guard<std::mutex> aGuard(g_aLOKMutex);
if ( priv->m_pDocument )
{
priv->m_pDocument->pClass->destroy( priv->m_pDocument );
priv->m_pDocument = nullptr;
}
priv->m_pOffice->pClass->registerCallback(priv->m_pOffice, globalCallbackWorker, pDocView);
priv->m_pDocument = priv->m_pOffice->pClass->documentLoad( priv->m_pOffice, priv->m_aDocPath.c_str() );
if ( !priv->m_pDocument )
{
char *pError = priv->m_pOffice->pClass->getError( priv->m_pOffice );
g_task_return_new_error(task, g_quark_from_static_string ("LOK error"), 0, "%s", pError);
}
else
{
priv->m_eDocumentType = static_cast<LibreOfficeKitDocumentType>(priv->m_pDocument->pClass->getDocumentType(priv->m_pDocument));
gdk_threads_add_idle(postDocumentLoad, pDocView);
g_task_return_boolean (task, true);
}
}
static void
setPartInThread(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));
int nPart = pLOEvent->m_nPart;
std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
std::stringstream ss;
ss << "lok::Document::setView(" << priv->m_nViewId << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
priv->m_pDocument->pClass->setPart( priv->m_pDocument, nPart );
aGuard.unlock();
lok_doc_view_reset_view(pDocView);
}
static void
setPartmodeInThread(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));
int nPartMode = pLOEvent->m_nPartMode;
std::lock_guard<std::mutex> aGuard(g_aLOKMutex);
std::stringstream ss;
ss << "lok::Document::setView(" << priv->m_nViewId << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
priv->m_pDocument->pClass->setPartMode( priv->m_pDocument, nPartMode );
}
static void
setEditInThread(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));
gboolean bWasEdit = priv->m_bEdit;
gboolean bEdit = pLOEvent->m_bEdit;
if (!priv->m_bEdit && bEdit)
g_info("lok_doc_view_set_edit: entering edit mode");
else if (priv->m_bEdit && !bEdit)
{
g_info("lok_doc_view_set_edit: leaving edit mode");
std::lock_guard<std::mutex> aGuard(g_aLOKMutex);
std::stringstream ss;
ss << "lok::Document::setView(" << priv->m_nViewId << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
priv->m_pDocument->pClass->resetSelection(priv->m_pDocument);
}
priv->m_bEdit = bEdit;
g_signal_emit(pDocView, doc_view_signals[EDIT_CHANGED], 0, bWasEdit);
gdk_threads_add_idle(queueDraw, GTK_WIDGET(pDocView));
}
static void
postCommandInThread (gpointer data)
{
GTask* task = G_TASK(data);
LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
LOKDocViewPrivate& priv = getPrivate(pDocView);
std::lock_guard<std::mutex> aGuard(g_aLOKMutex);
std::stringstream ss;
ss << "lok::Document::setView(" << priv->m_nViewId << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
ss.str(std::string());
ss << "lok::Document::postUnoCommand(" << pLOEvent->m_pCommand << ", " << pLOEvent->m_pArguments << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->postUnoCommand(priv->m_pDocument, pLOEvent->m_pCommand, pLOEvent->m_pArguments, pLOEvent->m_bNotifyWhenFinished);
}
static void
paintTileInThread (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));
// check if "source" tile buffer is different from "current" tile buffer
if (pLOEvent->m_pTileBuffer != &*priv->m_pTileBuffer)
{
pLOEvent->m_pTileBuffer = nullptr;
g_task_return_new_error(task,
LOK_TILEBUFFER_ERROR,
LOK_TILEBUFFER_CHANGED,
"TileBuffer has changed");
return;
}
std::unique_ptr<TileBuffer>& buffer = priv->m_pTileBuffer;
int index = pLOEvent->m_nPaintTileX * buffer->m_nWidth + pLOEvent->m_nPaintTileY;
if (buffer->m_mTiles.find(index) != buffer->m_mTiles.end() &&
buffer->m_mTiles[index].valid)
return;
cairo_surface_t *pSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nTileSizePixels, nTileSizePixels);
if (cairo_surface_status(pSurface) != CAIRO_STATUS_SUCCESS)
{
cairo_surface_destroy(pSurface);
g_task_return_new_error(task,
LOK_TILEBUFFER_ERROR,
LOK_TILEBUFFER_MEMORY,
"Error allocating Surface");
return;
}
unsigned char* pBuffer = cairo_image_surface_get_data(pSurface);
GdkRectangle aTileRectangle;
aTileRectangle.x = pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom) * pLOEvent->m_nPaintTileY;
aTileRectangle.y = pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom) * pLOEvent->m_nPaintTileX;
std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
std::stringstream ss;
ss << "lok::Document::setView(" << priv->m_nViewId << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
ss.str(std::string());
GTimer* aTimer = g_timer_new();
gulong nElapsedMs;
ss << "lok::Document::paintTile(" << static_cast<void*>(pBuffer) << ", "
<< nTileSizePixels << ", " << nTileSizePixels << ", "
<< aTileRectangle.x << ", " << aTileRectangle.y << ", "
<< pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom) << ", "
<< pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom) << ")";
priv->m_pDocument->pClass->paintTile(priv->m_pDocument,
pBuffer,
nTileSizePixels, nTileSizePixels,
aTileRectangle.x, aTileRectangle.y,
pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom),
pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom));
aGuard.unlock();
g_timer_elapsed(aTimer, &nElapsedMs);
ss << " rendered in " << (nElapsedMs / 1000.) << " milliseconds";
g_info("%s", ss.str().c_str());
g_timer_destroy(aTimer);
cairo_surface_mark_dirty(pSurface);
// Its likely that while the tilebuffer has changed, one of the paint tile
// requests has passed the previous check at start of this function, and has
// rendered the tile already. We want to stop such rendered tiles from being
// stored in new tile buffer.
if (pLOEvent->m_pTileBuffer != &*priv->m_pTileBuffer)
{
pLOEvent->m_pTileBuffer = nullptr;
g_task_return_new_error(task,
LOK_TILEBUFFER_ERROR,
LOK_TILEBUFFER_CHANGED,
"TileBuffer has changed");
return;
}
g_task_return_pointer(task, pSurface, reinterpret_cast<GDestroyNotify>(cairo_surface_destroy));
}
static void
lokThreadFunc(gpointer data, gpointer /*user_data*/)
{
GTask* task = G_TASK(data);
LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
LOKDocViewPrivate& priv = getPrivate(pDocView);
switch (pLOEvent->m_nType)
{
case LOK_LOAD_DOC:
openDocumentInThread(task);
break;
case LOK_POST_COMMAND:
postCommandInThread(task);
break;
case LOK_SET_EDIT:
setEditInThread(task);
break;
case LOK_SET_PART:
setPartInThread(task);
break;
case LOK_SET_PARTMODE:
setPartmodeInThread(task);
break;
case LOK_POST_KEY:
// view-only/editable mode already checked during signal key signal emission
postKeyEventInThread(task);
break;
case LOK_PAINT_TILE:
paintTileInThread(task);
break;
case LOK_POST_MOUSE_EVENT:
postMouseEventInThread(task);
break;
case LOK_SET_GRAPHIC_SELECTION:
if (priv->m_bEdit)
setGraphicSelectionInThread(task);
else
g_info ("LOK_SET_GRAPHIC_SELECTION: skipping graphical operation in view-only mode");
break;
case LOK_SET_CLIENT_ZOOM:
setClientZoomInThread(task);
break;
}
g_object_unref(task);
}
static void lok_doc_view_init (LOKDocView* pDocView)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
priv.m_pImpl = new LOKDocViewPrivateImpl();
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);
priv->lokThreadPool = g_thread_pool_new(lokThreadFunc,
nullptr,
1,
FALSE,
nullptr);
}
static void lok_doc_view_set_property (GObject* object, guint propId, const GValue *value, GParamSpec *pspec)
{
LOKDocView* pDocView = LOK_DOC_VIEW (object);
LOKDocViewPrivate& priv = getPrivate(pDocView);
gboolean bDocPasswordEnabled = priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD;
gboolean bDocPasswordToModifyEnabled = priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY;
gboolean bTiledAnnotationsEnabled = !(priv->m_nLOKFeatures & LOK_FEATURE_NO_TILED_ANNOTATIONS);
switch (propId)
{
case PROP_LO_PATH:
priv->m_aLOPath = g_value_get_string (value);
break;
case PROP_LO_POINTER:
priv->m_pOffice = static_cast<LibreOfficeKit*>(g_value_get_pointer(value));
break;
case PROP_USER_PROFILE_URL:
if (const gchar* pUserProfile = g_value_get_string(value))
priv->m_aUserProfileURL = pUserProfile;
break;
case PROP_DOC_PATH:
priv->m_aDocPath = g_value_get_string (value);
break;
case PROP_DOC_POINTER:
priv->m_pDocument = static_cast<LibreOfficeKitDocument*>(g_value_get_pointer(value));
priv->m_eDocumentType = static_cast<LibreOfficeKitDocumentType>(priv->m_pDocument->pClass->getDocumentType(priv->m_pDocument));
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;
case PROP_DOC_PASSWORD:
if (g_value_get_boolean (value) != bDocPasswordEnabled)
{
priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_DOCUMENT_PASSWORD;
priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
}
break;
case PROP_DOC_PASSWORD_TO_MODIFY:
if ( g_value_get_boolean (value) != bDocPasswordToModifyEnabled)
{
priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY;
priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
}
break;
case PROP_TILED_ANNOTATIONS:
if ( g_value_get_boolean (value) != bTiledAnnotationsEnabled)
{
priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_NO_TILED_ANNOTATIONS;
priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec);
}
}
static void lok_doc_view_get_property (GObject* object, guint propId, GValue *value, GParamSpec *pspec)
{
LOKDocView* pDocView = LOK_DOC_VIEW (object);
LOKDocViewPrivate& priv = getPrivate(pDocView);
switch (propId)
{
case PROP_LO_PATH:
g_value_set_string (value, priv->m_aLOPath.c_str());
break;
case PROP_LO_POINTER:
g_value_set_pointer(value, priv->m_pOffice);
break;
case PROP_USER_PROFILE_URL:
g_value_set_string(value, priv->m_aUserProfileURL.c_str());
break;
case PROP_DOC_PATH:
g_value_set_string (value, priv->m_aDocPath.c_str());
break;
case PROP_DOC_POINTER:
g_value_set_pointer(value, priv->m_pDocument);
break;
case PROP_EDITABLE:
g_value_set_boolean (value, priv->m_bEdit);
break;
case PROP_LOAD_PROGRESS:
g_value_set_double (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_IS_INITIALIZED:
g_value_set_boolean (value, priv->m_bInit);
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;
case PROP_DOC_PASSWORD:
g_value_set_boolean (value, priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD);
break;
case PROP_DOC_PASSWORD_TO_MODIFY:
g_value_set_boolean (value, priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY);
break;
case PROP_TILED_ANNOTATIONS:
g_value_set_boolean (value, !(priv->m_nLOKFeatures & LOK_FEATURE_NO_TILED_ANNOTATIONS));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec);
}
}
static gboolean lok_doc_view_draw (GtkWidget* pWidget, cairo_t* pCairo)
{
LOKDocView *pDocView = LOK_DOC_VIEW (pWidget);
renderDocument (pDocView, pCairo);
renderOverlay (pDocView, pCairo);
return FALSE;
}
//rhbz#1444437 finalize may not occur immediately when this widget is destroyed
//it may happen during GC of javascript, e.g. in gnome-documents but "destroy"
//will be called promptly, so close documents in destroy, not finalize
static void lok_doc_view_destroy (GtkWidget* widget)
{
LOKDocView* pDocView = LOK_DOC_VIEW (widget);
LOKDocViewPrivate& priv = getPrivate(pDocView);
// Ignore notifications sent to this view on shutdown.
std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
std::stringstream ss;
ss << "lok::Document::setView(" << priv->m_nViewId << ")";
g_info("%s", ss.str().c_str());
if (priv->m_pDocument)
{
priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
priv->m_pDocument->pClass->registerCallback(priv->m_pDocument, nullptr, nullptr);
}
if (priv->lokThreadPool)
{
g_thread_pool_free(priv->lokThreadPool, true, true);
priv->lokThreadPool = nullptr;
}
aGuard.unlock();
if (priv->m_pDocument)
{
if (priv->m_pDocument->pClass->getViewsCount(priv->m_pDocument) > 1)
{
priv->m_pDocument->pClass->destroyView(priv->m_pDocument, priv->m_nViewId);
}
else
{
if (priv->m_pDocument)
{
priv->m_pDocument->pClass->destroy (priv->m_pDocument);
priv->m_pDocument = nullptr;
}
if (priv->m_pOffice)
{
priv->m_pOffice->pClass->destroy (priv->m_pOffice);
priv->m_pOffice = nullptr;
}
}
}
GTK_WIDGET_CLASS (lok_doc_view_parent_class)->destroy (widget);
}
static void lok_doc_view_finalize (GObject* object)
{
LOKDocView* pDocView = LOK_DOC_VIEW (object);
LOKDocViewPrivate& priv = getPrivate(pDocView);
delete priv.m_pImpl;
priv.m_pImpl = nullptr;
G_OBJECT_CLASS (lok_doc_view_parent_class)->finalize (object);
}
static gboolean lok_doc_view_initable_init (GInitable *initable, GCancellable* /*cancellable*/, GError **error)
{
LOKDocView *pDocView = LOK_DOC_VIEW (initable);
LOKDocViewPrivate& priv = getPrivate(pDocView);
if (priv->m_pOffice != nullptr)
return TRUE;
priv->m_pOffice = lok_init_2(priv->m_aLOPath.c_str(), priv->m_aUserProfileURL.empty() ? nullptr : priv->m_aUserProfileURL.c_str());
if (priv->m_pOffice == nullptr)
{
g_set_error (error,
g_quark_from_static_string ("LOK initialization error"), 0,
"Failed to get LibreOfficeKit context. Make sure path (%s) is correct",
priv->m_aLOPath.c_str());
return FALSE;
}
priv->m_nLOKFeatures |= LOK_FEATURE_PART_IN_INVALIDATION_CALLBACK;
priv->m_nLOKFeatures |= LOK_FEATURE_VIEWID_IN_VISCURSOR_INVALIDATION_CALLBACK;
priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
return TRUE;
}
static void lok_doc_view_initable_iface_init (GInitableIface *iface)
{
iface->init = lok_doc_view_initable_init;
}
static void lok_doc_view_class_init (LOKDocViewClass* pClass)
{
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;
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->key_press_event = signalKey;
pWidgetClass->key_release_event = signalKey;
pWidgetClass->motion_notify_event = lok_doc_view_signal_motion;
pWidgetClass->destroy = lok_doc_view_destroy;
/**
* LOKDocView:lopath:
*
* The absolute path of the LibreOffice install.
*/
properties[PROP_LO_PATH] =
g_param_spec_string("lopath",
"LO Path",
"LibreOffice Install Path",
nullptr,
static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* LOKDocView:lopointer:
*
* A LibreOfficeKit* in case lok_init() is already called
* previously.
*/
properties[PROP_LO_POINTER] =
g_param_spec_pointer("lopointer",
"LO Pointer",
"A LibreOfficeKit* from lok_init()",
static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* LOKDocView:userprofileurl:
*
* The absolute path of the LibreOffice user profile.
*/
properties[PROP_USER_PROFILE_URL] =
g_param_spec_string("userprofileurl",
"User profile path",
"LibreOffice user profile path",
nullptr,
static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* LOKDocView:docpath:
*
* The path of the document that is currently being viewed.
*/
properties[PROP_DOC_PATH] =
g_param_spec_string("docpath",
"Document Path",
"The URI of the document to open",
nullptr,
static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* LOKDocView:docpointer:
*
* A LibreOfficeKitDocument* in case documentLoad() is already called
* previously.
*/
properties[PROP_DOC_POINTER] =
g_param_spec_pointer("docpointer",
"Document Pointer",
"A LibreOfficeKitDocument* from documentLoad()",
static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* LOKDocView:editable:
*
* Whether the document loaded inside of #LOKDocView is editable or not.
*/
properties[PROP_EDITABLE] =
g_param_spec_boolean("editable",
"Editable",
"Whether the content is in edit mode or not",
FALSE,
static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* 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.
*/
properties[PROP_LOAD_PROGRESS] =
g_param_spec_double("load-progress",
"Estimated Load Progress",
"Shows the progress of the document load operation",
0.0, 1.0, 0.0,
static_cast<GParamFlags>(G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* LOKDocView:zoom-level:
*
* The current zoom level of the document loaded inside #LOKDocView. The
* default value is 1.0.
*/
properties[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_STATIC_STRINGS));
/**
* LOKDocView:is-loading:
*
* Whether the requested document is being loaded or not. %TRUE if it is
* being loaded, otherwise %FALSE.
*/
properties[PROP_IS_LOADING] =
g_param_spec_boolean("is-loading",
"Is Loading",
"Whether the view is loading a document",
FALSE,
static_cast<GParamFlags>(G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* LOKDocView:is-initialized:
*
* Whether the requested document has completely loaded or not.
*/
properties[PROP_IS_INITIALIZED] =
g_param_spec_boolean("is-initialized",
"Has initialized",
"Whether the view has completely initialized",
FALSE,
static_cast<GParamFlags>(G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* LOKDocView:doc-width:
*
* The width of the currently loaded document in #LOKDocView in twips.
*/
properties[PROP_DOC_WIDTH] =
g_param_spec_long("doc-width",
"Document Width",
"Width of the document in twips",
0, G_MAXLONG, 0,
static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* LOKDocView:doc-height:
*
* The height of the currently loaded document in #LOKDocView in twips.
*/
properties[PROP_DOC_HEIGHT] =
g_param_spec_long("doc-height",
"Document Height",
"Height of the document in twips",
0, G_MAXLONG, 0,
static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* LOKDocView:can-zoom-in:
*
* It tells whether the view can further be zoomed in or not.
*/
properties[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.
*/
properties[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:doc-password:
*
* Set it to true if client supports providing password for viewing
* password protected documents
*/
properties[PROP_DOC_PASSWORD] =
g_param_spec_boolean("doc-password",
"Document password capability",
"Whether client supports providing document passwords",
FALSE,
static_cast<GParamFlags>(G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS));
/**
* LOKDocView:doc-password-to-modify:
*
* Set it to true if client supports providing password for edit-protected documents
*/
properties[PROP_DOC_PASSWORD_TO_MODIFY] =
g_param_spec_boolean("doc-password-to-modify",
"Edit document password capability",
"Whether the client supports providing passwords to edit documents",
FALSE,
static_cast<GParamFlags>(G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS));
/**
* LOKDocView:tiled-annotations-rendering:
*
* Set it to false if client does not want LO to render comments in tiles and
* instead interested in using comments API to access comments
*/
properties[PROP_TILED_ANNOTATIONS] =
g_param_spec_boolean("tiled-annotations",
"Render comments in tiles",
"Whether the client wants in tile comment rendering",
TRUE,
static_cast<GParamFlags>(G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS));
g_object_class_install_properties(pGObjectClass, PROP_LAST, properties);
/**
* LOKDocView::load-changed:
* @pDocView: the #LOKDocView on which the signal is emitted
* @fLoadProgress: the new progress value
*/
doc_view_signals[LOAD_CHANGED] =
g_signal_new("load-changed",
G_TYPE_FROM_CLASS (pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__DOUBLE,
G_TYPE_NONE, 1,
G_TYPE_DOUBLE);
/**
* 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 (pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__BOOLEAN,
G_TYPE_NONE, 1,
G_TYPE_BOOLEAN);
/**
* 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(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/**
* 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(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/**
* 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(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__INT,
G_TYPE_NONE, 1,
G_TYPE_INT);
/**
* LOKDocView::size-changed:
* @pDocView: the #LOKDocView on which the signal is emitted
* @aCommand: NULL, we just notify that want to notify the UI elements that are interested.
*/
doc_view_signals[SIZE_CHANGED] =
g_signal_new("size-changed",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 1,
G_TYPE_INT);
/**
* LOKDocView::hyperlinked-clicked:
* @pDocView: the #LOKDocView on which the signal is emitted
* @aHyperlink: the URI which the application should handle
*/
doc_view_signals[HYPERLINK_CLICKED] =
g_signal_new("hyperlink-clicked",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/**
* LOKDocView::cursor-changed:
* @pDocView: the #LOKDocView on which the signal is emitted
* @nX: The new cursor position (X coordinate) in pixels
* @nY: The new cursor position (Y coordinate) in pixels
* @nWidth: The width of new cursor
* @nHeight: The height of new cursor
*/
doc_view_signals[CURSOR_CHANGED] =
g_signal_new("cursor-changed",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_generic,
G_TYPE_NONE, 4,
G_TYPE_INT, G_TYPE_INT,
G_TYPE_INT, G_TYPE_INT);
/**
* LOKDocView::search-result-count:
* @pDocView: the #LOKDocView on which the signal is emitted
* @aCommand: number of matches.
*/
doc_view_signals[SEARCH_RESULT_COUNT] =
g_signal_new("search-result-count",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/**
* LOKDocView::command-result:
* @pDocView: the #LOKDocView on which the signal is emitted
* @aCommand: JSON containing the info about the command that finished,
* and its success status.
*/
doc_view_signals[COMMAND_RESULT] =
g_signal_new("command-result",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/**
* LOKDocView::address-changed:
* @pDocView: the #LOKDocView on which the signal is emitted
* @aCommand: formula text content
*/
doc_view_signals[ADDRESS_CHANGED] =
g_signal_new("address-changed",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/**
* LOKDocView::formula-changed:
* @pDocView: the #LOKDocView on which the signal is emitted
* @aCommand: formula text content
*/
doc_view_signals[FORMULA_CHANGED] =
g_signal_new("formula-changed",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/**
* LOKDocView::text-selection:
* @pDocView: the #LOKDocView on which the signal is emitted
* @bIsTextSelected: whether text selected is non-null
*/
doc_view_signals[TEXT_SELECTION] =
g_signal_new("text-selection",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__BOOLEAN,
G_TYPE_NONE, 1,
G_TYPE_BOOLEAN);
/**
* LOKDocView::password-required:
* @pDocView: the #LOKDocView on which the signal is emitted
* @pUrl: URL of the document for which password is required
* @bModify: whether password id required to modify the document
* This is true when password is required to edit the document,
* while it can still be viewed without password. In such cases, provide a NULL
* password for read-only access to the document.
* If false, password is required for opening the document, and document
* cannot be opened without providing a valid password.
*
* Password must be provided by calling lok_doc_view_set_document_password
* function with pUrl as provided by the callback.
*
* Upon entering a invalid password, another `password-required` signal is
* emitted.
* Upon entering a valid password, document starts to load.
* Upon entering a NULL password: if bModify is %TRUE, document starts to
* open in view-only mode, else loading of document is aborted.
*/
doc_view_signals[PASSWORD_REQUIRED] =
g_signal_new("password-required",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_generic,
G_TYPE_NONE, 2,
G_TYPE_STRING,
G_TYPE_BOOLEAN);
/**
* LOKDocView::comment:
* @pDocView: the #LOKDocView on which the signal is emitted
* @pComment: the JSON string containing comment notification
* The has following structure containing the information telling whether
* the comment has been added, deleted or modified.
* The example:
* {
* "comment": {
* "action": "Add",
* "id": "11",
* "parent": "4",
* "author": "Unknown Author",
* "text": "This is a comment",
* "dateTime": "2016-08-18T13:13:00",
* "anchorPos": "4529, 3906",
* "textRange": "1418, 3906, 3111, 919"
* }
* }
* 'action' can be 'Add', 'Remove' or 'Modify' depending on whether
* comment has been added, removed or modified.
* 'parent' is a non-zero comment id if this comment is a reply comment,
* otherwise its a root comment.
*/
doc_view_signals[COMMENT] =
g_signal_new("comment",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_generic,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/**
* LOKDocView::ruler:
* @pDocView: the #LOKDocView on which the signal is emitted
* @pPayload: the JSON string containing the information about ruler properties
*
* The payload format is:
*
* {
* "margin1": "...",
* "margin2": "...",
* "leftOffset": "...",
* "pageOffset": "...",
* "pageWidth": "...",
* "unit": "..."
* }
*/
doc_view_signals[RULER] =
g_signal_new("ruler",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_generic,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/**
* LOKDocView::window::
* @pDocView: the #LOKDocView on which the signal is emitted
* @pPayload: the JSON string containing the information about the window
*
* This signal emits information about external windows like dialogs, autopopups for now.
*
* The payload format of pPayload is:
*
* {
* "id": "unique integer id of the dialog",
* "action": "<see below>",
* "type": "<see below>"
* "rectangle": "x, y, width, height"
* }
*
* "type" tells the type of the window the action is associated with
* - "dialog" - window is a dialog
* - "child" - window is a floating window (combo boxes, etc.)
*
* "action" can take following values:
* - "created" - window is created in the backend, client can render it now
* - "title_changed" - window's title is changed
* - "size_changed" - window's size is changed
* - "invalidate" - the area as described by "rectangle" is invalidated
* Clients must request the new area
* - "cursor_invalidate" - cursor is invalidated. New position is in "rectangle"
* - "cursor_visible" - cursor visible status is changed. Status is available
* in "visible" field
* - "close" - window is closed
*/
doc_view_signals[WINDOW] =
g_signal_new("window",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_generic,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/**
* LOKDocView::invalidate-header::
* @pDocView: the #LOKDocView on which the signal is emitted
* @pPayload: can be either "row", "column", or "all".
*
* The column/row header is no more valid because of a column/row insertion
* or a similar event. Clients must query a new column/row header set.
*
* The payload says if we are invalidating a row or column header
*/
doc_view_signals[INVALIDATE_HEADER] =
g_signal_new("invalidate-header",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_generic,
G_TYPE_NONE, 1,
G_TYPE_STRING);
}
SAL_DLLPUBLIC_EXPORT GtkWidget*
lok_doc_view_new (const gchar* pPath, GCancellable *cancellable, GError **error)
{
return GTK_WIDGET (g_initable_new (LOK_TYPE_DOC_VIEW, cancellable, error,
"lopath", pPath == nullptr ? LOK_PATH : pPath,
"halign", GTK_ALIGN_CENTER,
"valign", GTK_ALIGN_CENTER,
nullptr));
}
SAL_DLLPUBLIC_EXPORT GtkWidget*
lok_doc_view_new_from_user_profile (const gchar* pPath, const gchar* pUserProfile, GCancellable *cancellable, GError **error)
{
return GTK_WIDGET(g_initable_new(LOK_TYPE_DOC_VIEW, cancellable, error,
"lopath", pPath == nullptr ? LOK_PATH : pPath,
"userprofileurl", pUserProfile,
"halign", GTK_ALIGN_CENTER,
"valign", GTK_ALIGN_CENTER,
nullptr));
}
SAL_DLLPUBLIC_EXPORT GtkWidget* lok_doc_view_new_from_widget(LOKDocView* pOldLOKDocView,
const gchar* pRenderingArguments)
{
LOKDocViewPrivate& pOldPriv = getPrivate(pOldLOKDocView);
GtkWidget* pNewDocView = GTK_WIDGET(g_initable_new(LOK_TYPE_DOC_VIEW, /*cancellable=*/nullptr, /*error=*/nullptr,
"lopath", pOldPriv->m_aLOPath.c_str(),
"userprofileurl", pOldPriv->m_aUserProfileURL.c_str(),
"lopointer", pOldPriv->m_pOffice,
"docpointer", pOldPriv->m_pDocument,
"halign", GTK_ALIGN_CENTER,
"valign", GTK_ALIGN_CENTER,
nullptr));
// No documentLoad(), just a createView().
LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(pNewDocView));
LOKDocViewPrivate& pNewPriv = getPrivate(LOK_DOC_VIEW(pNewDocView));
// Store the view id only later in postDocumentLoad(), as
// initializeForRendering() changes the id in Impress.
pDocument->pClass->createView(pDocument);
pNewPriv->m_aRenderingArguments = pRenderingArguments;
postDocumentLoad(pNewDocView);
return pNewDocView;
}
SAL_DLLPUBLIC_EXPORT gboolean
lok_doc_view_open_document_finish (LOKDocView* pDocView, GAsyncResult* res, GError** error)
{
GTask* task = G_TASK(res);
g_return_val_if_fail(g_task_is_valid(res, pDocView), false);
g_return_val_if_fail(g_task_get_source_tag(task) == lok_doc_view_open_document, false);
g_return_val_if_fail(error == nullptr || *error == nullptr, false);
return g_task_propagate_boolean(task, error);
}
SAL_DLLPUBLIC_EXPORT void
lok_doc_view_open_document (LOKDocView* pDocView,
const gchar* pPath,
const gchar* pRenderingArguments,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer userdata)
{
GTask* task = g_task_new(pDocView, cancellable, callback, userdata);
LOKDocViewPrivate& priv = getPrivate(pDocView);
GError* error = nullptr;
LOEvent* pLOEvent = new LOEvent(LOK_LOAD_DOC);
g_object_set(G_OBJECT(pDocView), "docpath", pPath, nullptr);
if (pRenderingArguments)
priv->m_aRenderingArguments = pRenderingArguments;
g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
g_task_set_source_tag(task, reinterpret_cast<gpointer>(lok_doc_view_open_document));
g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
if (error != nullptr)
{
g_warning("Unable to call LOK_LOAD_DOC: %s", error->message);
g_clear_error(&error);
}
g_object_unref(task);
}
SAL_DLLPUBLIC_EXPORT LibreOfficeKitDocument*
lok_doc_view_get_document (LOKDocView* pDocView)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
return priv->m_pDocument;
}
SAL_DLLPUBLIC_EXPORT void
lok_doc_view_set_visible_area (LOKDocView* pDocView, GdkRectangle* pVisibleArea)
{
if (!pVisibleArea)
return;
LOKDocViewPrivate& priv = getPrivate(pDocView);
priv->m_aVisibleArea = *pVisibleArea;
priv->m_bVisibleAreaSet = true;
}
namespace {
// This used to be rtl::math::approxEqual() but since that isn't inline anymore
// in rtl/math.hxx and was moved into libuno_sal as rtl_math_approxEqual() to
// cater for representable integer cases and we don't want to link against
// libuno_sal, we'll have to have an own implementation. The special large
// integer cases seems not be needed here.
inline bool lok_approxEqual(double a, double b)
{
static const double e48 = 1.0 / (16777216.0 * 16777216.0);
if (a == b)
return true;
if (a == 0.0 || b == 0.0)
return false;
const double d = fabs(a - b);
return (d < fabs(a) * e48 && d < fabs(b) * e48);
}
}
SAL_DLLPUBLIC_EXPORT void
lok_doc_view_set_zoom (LOKDocView* pDocView, float fZoom)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
GError* error = nullptr;
if (!priv->m_pDocument)
return;
// Clamp the input value in [MIN_ZOOM, MAX_ZOOM]
fZoom = fZoom < MIN_ZOOM ? MIN_ZOOM : fZoom;
fZoom = std::min(fZoom, MAX_ZOOM);
if (lok_approxEqual(fZoom, priv->m_fZoom))
return;
priv->m_fZoom = fZoom;
long nDocumentWidthPixels = twipToPixel(priv->m_nDocumentWidthTwips, fZoom);
long nDocumentHeightPixels = twipToPixel(priv->m_nDocumentHeightTwips, fZoom);
// Total number of columns in this document.
guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixels);
priv->m_pTileBuffer = std::unique_ptr<TileBuffer>(new TileBuffer(nColumns));
gtk_widget_set_size_request(GTK_WIDGET(pDocView),
nDocumentWidthPixels,
nDocumentHeightPixels);
g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_ZOOM]);
// set properties to indicate if view can be further zoomed in/out
bool bCanZoomIn = priv->m_fZoom < MAX_ZOOM;
bool bCanZoomOut = priv->m_fZoom > MIN_ZOOM;
if (bCanZoomIn != bool(priv->m_bCanZoomIn))
{
priv->m_bCanZoomIn = bCanZoomIn;
g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_CAN_ZOOM_IN]);
}
if (bCanZoomOut != bool(priv->m_bCanZoomOut))
{
priv->m_bCanZoomOut = bCanZoomOut;
g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_CAN_ZOOM_OUT]);
}
// Update the client's view size
GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
LOEvent* pLOEvent = new LOEvent(LOK_SET_CLIENT_ZOOM);
pLOEvent->m_nTilePixelWidth = nTileSizePixels;
pLOEvent->m_nTilePixelHeight = nTileSizePixels;
pLOEvent->m_nTileTwipWidth = pixelToTwip(nTileSizePixels, fZoom);
pLOEvent->m_nTileTwipHeight = pixelToTwip(nTileSizePixels, 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_CLIENT_ZOOM: %s", error->message);
g_clear_error(&error);
}
g_object_unref(task);
priv->m_nTileSizeTwips = pixelToTwip(nTileSizePixels, priv->m_fZoom);
}
SAL_DLLPUBLIC_EXPORT gfloat
lok_doc_view_get_zoom (LOKDocView* pDocView)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
return priv->m_fZoom;
}
SAL_DLLPUBLIC_EXPORT gint
lok_doc_view_get_parts (LOKDocView* pDocView)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
if (!priv->m_pDocument)
return -1;
std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
std::stringstream ss;
ss << "lok::Document::setView(" << priv->m_nViewId << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
return priv->m_pDocument->pClass->getParts( priv->m_pDocument );
}
SAL_DLLPUBLIC_EXPORT gint
lok_doc_view_get_part (LOKDocView* pDocView)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
if (!priv->m_pDocument)
return -1;
std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
std::stringstream ss;
ss << "lok::Document::setView(" << priv->m_nViewId << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
return priv->m_pDocument->pClass->getPart( priv->m_pDocument );
}
SAL_DLLPUBLIC_EXPORT void
lok_doc_view_set_part (LOKDocView* pDocView, int nPart)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
if (!priv->m_pDocument)
return;
if (nPart < 0 || nPart >= priv->m_nParts)
{
g_warning("Invalid part request : %d", nPart);
return;
}
GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
LOEvent* pLOEvent = new LOEvent(LOK_SET_PART);
GError* error = nullptr;
pLOEvent->m_nPart = nPart;
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_PART: %s", error->message);
g_clear_error(&error);
}
g_object_unref(task);
priv->m_nPartId = nPart;
}
SAL_DLLPUBLIC_EXPORT gchar*
lok_doc_view_get_part_name (LOKDocView* pDocView, int nPart)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
if (!priv->m_pDocument)
return nullptr;
std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
std::stringstream ss;
ss << "lok::Document::setView(" << priv->m_nViewId << ")";
g_info("%s", ss.str().c_str());
priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
return priv->m_pDocument->pClass->getPartName( priv->m_pDocument, nPart );
}
SAL_DLLPUBLIC_EXPORT void
lok_doc_view_set_partmode(LOKDocView* pDocView,
int nPartMode)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
if (!priv->m_pDocument)
return;
GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
LOEvent* pLOEvent = new LOEvent(LOK_SET_PARTMODE);
GError* error = nullptr;
pLOEvent->m_nPartMode = nPartMode;
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_PARTMODE: %s", error->message);
g_clear_error(&error);
}
g_object_unref(task);
}
SAL_DLLPUBLIC_EXPORT void
lok_doc_view_reset_view(LOKDocView* pDocView)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
if (priv->m_pTileBuffer != nullptr)
priv->m_pTileBuffer->resetAllTiles();
priv->m_nLoadProgress = 0.0;
memset(&priv->m_aVisibleCursor, 0, sizeof(priv->m_aVisibleCursor));
priv->m_bCursorOverlayVisible = false;
priv->m_bCursorVisible = false;
priv->m_nLastButtonPressTime = 0;
priv->m_nLastButtonReleaseTime = 0;
priv->m_aTextSelectionRectangles.clear();
memset(&priv->m_aTextSelectionStart, 0, sizeof(priv->m_aTextSelectionStart));
memset(&priv->m_aTextSelectionEnd, 0, sizeof(priv->m_aTextSelectionEnd));
memset(&priv->m_aGraphicSelection, 0, sizeof(priv->m_aGraphicSelection));
priv->m_bInDragGraphicSelection = false;
memset(&priv->m_aCellCursor, 0, sizeof(priv->m_aCellCursor));
cairo_surface_destroy(priv->m_pHandleStart);
priv->m_pHandleStart = nullptr;
memset(&priv->m_aHandleStartRect, 0, sizeof(priv->m_aHandleStartRect));
priv->m_bInDragStartHandle = false;
cairo_surface_destroy(priv->m_pHandleMiddle);
priv->m_pHandleMiddle = nullptr;
memset(&priv->m_aHandleMiddleRect, 0, sizeof(priv->m_aHandleMiddleRect));
priv->m_bInDragMiddleHandle = false;
cairo_surface_destroy(priv->m_pHandleEnd);
priv->m_pHandleEnd = nullptr;
memset(&priv->m_aHandleEndRect, 0, sizeof(priv->m_aHandleEndRect));
priv->m_bInDragEndHandle = false;
memset(&priv->m_aGraphicHandleRects, 0, sizeof(priv->m_aGraphicHandleRects));
memset(&priv->m_bInDragGraphicHandles, 0, sizeof(priv->m_bInDragGraphicHandles));
gtk_widget_queue_draw(GTK_WIDGET(pDocView));
}
SAL_DLLPUBLIC_EXPORT void
lok_doc_view_set_edit(LOKDocView* pDocView,
gboolean bEdit)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
if (!priv->m_pDocument)
return;
GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
LOEvent* pLOEvent = new LOEvent(LOK_SET_EDIT);
GError* error = nullptr;
pLOEvent->m_bEdit = bEdit;
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_EDIT: %s", error->message);
g_clear_error(&error);
}
g_object_unref(task);
}
SAL_DLLPUBLIC_EXPORT gboolean
lok_doc_view_get_edit (LOKDocView* pDocView)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
return priv->m_bEdit;
}
SAL_DLLPUBLIC_EXPORT void
lok_doc_view_post_command (LOKDocView* pDocView,
const gchar* pCommand,
const gchar* pArguments,
gboolean bNotifyWhenFinished)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
if (!priv->m_pDocument)
return;
if (priv->m_bEdit)
LOKPostCommand(pDocView, pCommand, pArguments, bNotifyWhenFinished);
else
g_info ("LOK_POST_COMMAND: ignoring commands in view-only mode");
}
SAL_DLLPUBLIC_EXPORT void
lok_doc_view_find_prev (LOKDocView* pDocView,
const gchar* pText,
gboolean bHighlightAll)
{
doSearch(pDocView, pText, true, bHighlightAll);
}
SAL_DLLPUBLIC_EXPORT void
lok_doc_view_find_next (LOKDocView* pDocView,
const gchar* pText,
gboolean bHighlightAll)
{
doSearch(pDocView, pText, false, bHighlightAll);
}
SAL_DLLPUBLIC_EXPORT void
lok_doc_view_highlight_all (LOKDocView* pDocView,
const gchar* pText)
{
doSearch(pDocView, pText, false, true);
}
SAL_DLLPUBLIC_EXPORT gchar*
lok_doc_view_copy_selection (LOKDocView* pDocView,
const gchar* pMimeType,
gchar** pUsedMimeType)
{
LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(pDocView);
if (!pDocument)
return nullptr;
std::stringstream ss;
ss << "lok::Document::getTextSelection('" << pMimeType << "')";
g_info("%s", ss.str().c_str());
return pDocument->pClass->getTextSelection(pDocument, pMimeType, pUsedMimeType);
}
SAL_DLLPUBLIC_EXPORT gboolean
lok_doc_view_paste (LOKDocView* pDocView,
const gchar* pMimeType,
const gchar* pData,
gsize nSize)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
LibreOfficeKitDocument* pDocument = priv->m_pDocument;
gboolean ret = 0;
if (!pDocument)
return false;
if (!priv->m_bEdit)
{
g_info ("ignoring paste in view-only mode");
return ret;
}
if (pData)
{
std::stringstream ss;
ss << "lok::Document::paste('" << pMimeType << "', '" << std::string(pData, nSize) << ", "<<nSize<<"')";
g_info("%s", ss.str().c_str());
ret = pDocument->pClass->paste(pDocument, pMimeType, pData, nSize);
}
return ret;
}
SAL_DLLPUBLIC_EXPORT void
lok_doc_view_set_document_password (LOKDocView* pDocView,
const gchar* pURL,
const gchar* pPassword)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
priv->m_pOffice->pClass->setDocumentPassword(priv->m_pOffice, pURL, pPassword);
}
SAL_DLLPUBLIC_EXPORT gchar*
lok_doc_view_get_version_info (LOKDocView* pDocView)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
return priv->m_pOffice->pClass->getVersionInfo(priv->m_pOffice);
}
SAL_DLLPUBLIC_EXPORT gfloat
lok_doc_view_pixel_to_twip (LOKDocView* pDocView, float fInput)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
return pixelToTwip(fInput, priv->m_fZoom);
}
SAL_DLLPUBLIC_EXPORT gfloat
lok_doc_view_twip_to_pixel (LOKDocView* pDocView, float fInput)
{
LOKDocViewPrivate& priv = getPrivate(pDocView);
return twipToPixel(fInput, priv->m_fZoom);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */