LanguageTool: don't crash if REST protocol isn't set
[LibreOffice.git] / libreofficekit / source / gtk / lokdocview.cxx
blobce5b059a90065c772d9bbbd02031681c106ab2e4
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <sal/types.h>
11 #include <math.h>
12 #include <string.h>
13 #include <memory>
14 #include <vector>
15 #include <string>
16 #include <sstream>
17 #include <mutex>
18 #include <boost/property_tree/json_parser.hpp>
20 #include <com/sun/star/awt/Key.hpp>
21 #include <LibreOfficeKit/LibreOfficeKit.h>
22 #include <LibreOfficeKit/LibreOfficeKitInit.h>
23 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
24 #include <LibreOfficeKit/LibreOfficeKitGtk.h>
25 #include <vcl/event.hxx>
27 #include "tilebuffer.hxx"
29 #if !GLIB_CHECK_VERSION(2,32,0)
30 #define G_SOURCE_REMOVE FALSE
31 #define G_SOURCE_CONTINUE TRUE
32 #endif
33 #if !GLIB_CHECK_VERSION(2,40,0)
34 #define g_info(...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__)
35 #endif
37 // Cursor bitmaps from the installation set.
38 #define CURSOR_HANDLE_DIR "/../share/libreofficekit/"
39 // Number of handles around a graphic selection.
40 #define GRAPHIC_HANDLE_COUNT 8
41 // Maximum Zoom allowed
42 #define MAX_ZOOM 5.0f
43 // Minimum Zoom allowed
44 #define MIN_ZOOM 0.25f
46 /// This is expected to be locked during setView(), doSomethingElse() LOK calls.
47 static std::mutex g_aLOKMutex;
49 namespace {
51 /// Same as a GdkRectangle, but also tracks in which part the rectangle is.
52 struct ViewRectangle
54 int m_nPart;
55 GdkRectangle m_aRectangle;
57 ViewRectangle(int nPart = 0, const GdkRectangle& rRectangle = GdkRectangle())
58 : m_nPart(nPart),
59 m_aRectangle(rRectangle)
64 /// Same as a list of GdkRectangles, but also tracks in which part the rectangle is.
65 struct ViewRectangles
67 int m_nPart;
68 std::vector<GdkRectangle> m_aRectangles;
70 ViewRectangles(int nPart = 0, std::vector<GdkRectangle>&& rRectangles = std::vector<GdkRectangle>())
71 : m_nPart(nPart),
72 m_aRectangles(std::move(rRectangles))
77 /// Private struct used by this GObject type
78 struct LOKDocViewPrivateImpl
80 std::string m_aLOPath;
81 std::string m_aUserProfileURL;
82 std::string m_aDocPath;
83 std::string m_aRenderingArguments;
84 gdouble m_nLoadProgress;
85 bool m_bIsLoading;
86 bool m_bInit; // initializeForRendering() has been called
87 bool m_bCanZoomIn;
88 bool m_bCanZoomOut;
89 bool m_bUnipoll;
90 LibreOfficeKit* m_pOffice;
91 LibreOfficeKitDocument* m_pDocument;
93 std::unique_ptr<TileBuffer> m_pTileBuffer;
94 GThreadPool* lokThreadPool;
96 gfloat m_fZoom;
97 glong m_nDocumentWidthTwips;
98 glong m_nDocumentHeightTwips;
99 /// View or edit mode.
100 bool m_bEdit;
101 /// LOK Features
102 guint64 m_nLOKFeatures;
103 /// Number of parts in currently loaded document
104 gint m_nParts;
105 /// Position and size of the visible cursor.
106 GdkRectangle m_aVisibleCursor;
107 /// Position and size of the view cursors. The current view can only see
108 /// them, can't modify them. Key is the view id.
109 std::map<int, ViewRectangle> m_aViewCursors;
110 /// Cursor overlay is visible or hidden (for blinking).
111 bool m_bCursorOverlayVisible;
112 /// Cursor is visible or hidden (e.g. for graphic selection).
113 bool m_bCursorVisible;
114 /// Visibility of view selections. The current view can only see / them,
115 /// can't modify them. Key is the view id.
116 std::map<int, bool> m_aViewCursorVisibilities;
117 /// Time of the last button press.
118 guint32 m_nLastButtonPressTime;
119 /// Time of the last button release.
120 guint32 m_nLastButtonReleaseTime;
121 /// Last pressed button (left, right, middle)
122 guint32 m_nLastButtonPressed;
123 /// Key modifier (ctrl, atl, shift)
124 guint32 m_nKeyModifier;
125 /// Rectangles of the current text selection.
126 std::vector<GdkRectangle> m_aTextSelectionRectangles;
127 /// Rectangles of the current content control.
128 std::vector<GdkRectangle> m_aContentControlRectangles;
129 /// Alias/title of the current content control.
130 std::string m_aContentControlAlias;
131 /// Rectangles of view selections. The current view can only see
132 /// them, can't modify them. Key is the view id.
133 std::map<int, ViewRectangles> m_aTextViewSelectionRectangles;
134 /// Position and size of the selection start (as if there would be a cursor caret there).
135 GdkRectangle m_aTextSelectionStart;
136 /// Position and size of the selection end.
137 GdkRectangle m_aTextSelectionEnd;
138 GdkRectangle m_aGraphicSelection;
139 /// Position and size of the graphic view selections. The current view can only
140 /// see them, can't modify them. Key is the view id.
141 std::map<int, ViewRectangle> m_aGraphicViewSelections;
142 GdkRectangle m_aCellCursor;
143 /// Position and size of the cell view cursors. The current view can only
144 /// see them, can't modify them. Key is the view id.
145 std::map<int, ViewRectangle> m_aCellViewCursors;
146 bool m_bInDragGraphicSelection;
147 /// Position, size and color of the reference marks. The current view can only
148 /// see them, can't modify them. Key is the view id.
149 std::vector<std::pair<ViewRectangle, sal_uInt32>> m_aReferenceMarks;
151 /// @name Start/middle/end handle.
152 ///@{
153 /// Bitmap of the text selection start handle.
154 cairo_surface_t* m_pHandleStart;
155 /// Rectangle of the text selection start handle, to know if the user clicked on it or not
156 GdkRectangle m_aHandleStartRect;
157 /// If we are in the middle of a drag of the text selection end handle.
158 bool m_bInDragStartHandle;
159 /// Bitmap of the text selection middle handle.
160 cairo_surface_t* m_pHandleMiddle;
161 /// Rectangle of the text selection middle handle, to know if the user clicked on it or not
162 GdkRectangle m_aHandleMiddleRect;
163 /// If we are in the middle of a drag of the text selection middle handle.
164 bool m_bInDragMiddleHandle;
165 /// Bitmap of the text selection end handle.
166 cairo_surface_t* m_pHandleEnd;
167 /// Rectangle of the text selection end handle, to know if the user clicked on it or not
168 GdkRectangle m_aHandleEndRect;
169 /// If we are in the middle of a drag of the text selection end handle.
170 bool m_bInDragEndHandle;
171 ///@}
173 /// @name Graphic handles.
174 ///@{
175 /// Rectangle of a graphic selection handle, to know if the user clicked on it or not.
176 GdkRectangle m_aGraphicHandleRects[8];
177 /// If we are in the middle of a drag of a graphic selection handle.
178 bool m_bInDragGraphicHandles[8];
179 ///@}
181 /// View ID, returned by createView() or 0 by default.
182 int m_nViewId;
184 /// Cached part ID, returned by getPart().
185 int m_nPartId;
187 /// Cached document type, returned by getDocumentType().
188 LibreOfficeKitDocumentType m_eDocumentType;
190 /// Contains a freshly set zoom level: logic size of a tile.
191 /// It gets reset back to 0 when LOK was informed about this zoom change.
192 int m_nTileSizeTwips;
194 GdkRectangle m_aVisibleArea;
195 bool m_bVisibleAreaSet;
197 /// Event source ID for handleTimeout() of this widget.
198 guint m_nTimeoutId;
200 /// Rectangles of view locks. The current view can only see
201 /// them, can't modify them. Key is the view id.
202 std::map<int, ViewRectangle> m_aViewLockRectangles;
204 LOKDocViewPrivateImpl()
205 : m_nLoadProgress(0),
206 m_bIsLoading(false),
207 m_bInit(false),
208 m_bCanZoomIn(true),
209 m_bCanZoomOut(true),
210 m_bUnipoll(false),
211 m_pOffice(nullptr),
212 m_pDocument(nullptr),
213 lokThreadPool(nullptr),
214 m_fZoom(0),
215 m_nDocumentWidthTwips(0),
216 m_nDocumentHeightTwips(0),
217 m_bEdit(false),
218 m_nLOKFeatures(0),
219 m_nParts(0),
220 m_aVisibleCursor({0, 0, 0, 0}),
221 m_bCursorOverlayVisible(false),
222 m_bCursorVisible(true),
223 m_nLastButtonPressTime(0),
224 m_nLastButtonReleaseTime(0),
225 m_nLastButtonPressed(0),
226 m_nKeyModifier(0),
227 m_aTextSelectionStart({0, 0, 0, 0}),
228 m_aTextSelectionEnd({0, 0, 0, 0}),
229 m_aGraphicSelection({0, 0, 0, 0}),
230 m_aCellCursor({0, 0, 0, 0}),
231 m_bInDragGraphicSelection(false),
232 m_pHandleStart(nullptr),
233 m_aHandleStartRect({0, 0, 0, 0}),
234 m_bInDragStartHandle(false),
235 m_pHandleMiddle(nullptr),
236 m_aHandleMiddleRect({0, 0, 0, 0}),
237 m_bInDragMiddleHandle(false),
238 m_pHandleEnd(nullptr),
239 m_aHandleEndRect({0, 0, 0, 0}),
240 m_bInDragEndHandle(false),
241 m_nViewId(0),
242 m_nPartId(0),
243 m_eDocumentType(LOK_DOCTYPE_OTHER),
244 m_nTileSizeTwips(0),
245 m_aVisibleArea({0, 0, 0, 0}),
246 m_bVisibleAreaSet(false),
247 m_nTimeoutId(0)
249 memset(&m_aGraphicHandleRects, 0, sizeof(m_aGraphicHandleRects));
250 memset(&m_bInDragGraphicHandles, 0, sizeof(m_bInDragGraphicHandles));
253 ~LOKDocViewPrivateImpl()
255 if (m_nTimeoutId)
256 g_source_remove(m_nTimeoutId);
260 // Must be run with g_aLOKMutex locked
261 void setDocumentView(LibreOfficeKitDocument* pDoc, int viewId)
263 assert(pDoc);
264 std::stringstream ss;
265 ss << "lok::Document::setView(" << viewId << ")";
266 g_info("%s", ss.str().c_str());
267 pDoc->pClass->setView(pDoc, viewId);
271 /// Wrapper around LOKDocViewPrivateImpl, managed by malloc/memset/free.
272 struct _LOKDocViewPrivate
274 LOKDocViewPrivateImpl* m_pImpl;
276 LOKDocViewPrivateImpl* operator->()
278 return m_pImpl;
282 enum
284 LOAD_CHANGED,
285 EDIT_CHANGED,
286 COMMAND_CHANGED,
287 SEARCH_NOT_FOUND,
288 PART_CHANGED,
289 SIZE_CHANGED,
290 HYPERLINK_CLICKED,
291 CURSOR_CHANGED,
292 SEARCH_RESULT_COUNT,
293 COMMAND_RESULT,
294 ADDRESS_CHANGED,
295 FORMULA_CHANGED,
296 TEXT_SELECTION,
297 CONTENT_CONTROL,
298 PASSWORD_REQUIRED,
299 COMMENT,
300 RULER,
301 WINDOW,
302 INVALIDATE_HEADER,
304 LAST_SIGNAL
307 enum
309 PROP_0,
311 PROP_LO_PATH,
312 PROP_LO_UNIPOLL,
313 PROP_LO_POINTER,
314 PROP_USER_PROFILE_URL,
315 PROP_DOC_PATH,
316 PROP_DOC_POINTER,
317 PROP_EDITABLE,
318 PROP_LOAD_PROGRESS,
319 PROP_ZOOM,
320 PROP_IS_LOADING,
321 PROP_IS_INITIALIZED,
322 PROP_DOC_WIDTH,
323 PROP_DOC_HEIGHT,
324 PROP_CAN_ZOOM_IN,
325 PROP_CAN_ZOOM_OUT,
326 PROP_DOC_PASSWORD,
327 PROP_DOC_PASSWORD_TO_MODIFY,
328 PROP_TILED_ANNOTATIONS,
330 PROP_LAST
333 static guint doc_view_signals[LAST_SIGNAL] = { 0 };
334 static GParamSpec *properties[PROP_LAST] = { nullptr };
336 static void lok_doc_view_initable_iface_init (GInitableIface *iface);
337 static void callbackWorker (int nType, const char* pPayload, void* pData);
338 static void updateClientZoom (LOKDocView *pDocView);
340 #ifdef __GNUC__
341 #pragma GCC diagnostic push
342 #pragma GCC diagnostic ignored "-Wunused-function"
343 #if defined __clang__
344 #if __has_warning("-Wdeprecated-volatile")
345 #pragma clang diagnostic ignored "-Wdeprecated-volatile"
346 #endif
347 #endif
348 #endif
349 G_DEFINE_TYPE_WITH_CODE (LOKDocView, lok_doc_view, GTK_TYPE_DRAWING_AREA,
350 G_ADD_PRIVATE (LOKDocView)
351 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, lok_doc_view_initable_iface_init));
352 #ifdef __GNUC__
353 #pragma GCC diagnostic pop
354 #endif
356 static LOKDocViewPrivate& getPrivate(LOKDocView* pDocView)
358 LOKDocViewPrivate* priv = static_cast<LOKDocViewPrivate*>(lok_doc_view_get_instance_private(pDocView));
359 return *priv;
362 namespace {
364 /// Helper struct used to pass the data from soffice thread -> main thread.
365 struct CallbackData
367 int m_nType;
368 std::string m_aPayload;
369 LOKDocView* m_pDocView;
371 CallbackData(int nType, const std::string& rPayload, LOKDocView* pDocView)
372 : m_nType(nType),
373 m_aPayload(rPayload),
374 m_pDocView(pDocView) {}
379 static void
380 LOKPostCommand (LOKDocView* pDocView,
381 const gchar* pCommand,
382 const gchar* pArguments,
383 bool bNotifyWhenFinished)
385 LOKDocViewPrivate& priv = getPrivate(pDocView);
386 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
387 LOEvent* pLOEvent = new LOEvent(LOK_POST_COMMAND);
388 GError* error = nullptr;
389 pLOEvent->m_pCommand = g_strdup(pCommand);
390 pLOEvent->m_pArguments = g_strdup(pArguments);
391 pLOEvent->m_bNotifyWhenFinished = bNotifyWhenFinished;
393 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
394 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
395 if (error != nullptr)
397 g_warning("Unable to call LOK_POST_COMMAND: %s", error->message);
398 g_clear_error(&error);
400 g_object_unref(task);
403 static void
404 doSearch(LOKDocView* pDocView, const char* pText, bool bBackwards, bool highlightAll)
406 LOKDocViewPrivate& priv = getPrivate(pDocView);
407 if (!priv->m_pDocument)
408 return;
410 boost::property_tree::ptree aTree;
411 GtkWidget* drawingWidget = GTK_WIDGET(pDocView);
412 GdkWindow* drawingWindow = gtk_widget_get_window(drawingWidget);
413 if (!drawingWindow)
414 return;
415 std::shared_ptr<cairo_region_t> cairoVisRegion( gdk_window_get_visible_region(drawingWindow),
416 cairo_region_destroy);
417 cairo_rectangle_int_t cairoVisRect;
418 cairo_region_get_rectangle(cairoVisRegion.get(), 0, &cairoVisRect);
419 int x = pixelToTwip (cairoVisRect.x, priv->m_fZoom);
420 int y = pixelToTwip (cairoVisRect.y, priv->m_fZoom);
422 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchString/type", '/'), "string");
423 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchString/value", '/'), pText);
424 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Backward/type", '/'), "boolean");
425 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Backward/value", '/'), bBackwards);
426 if (highlightAll)
428 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Command/type", '/'), "unsigned short");
429 // SvxSearchCmd::FIND_ALL
430 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Command/value", '/'), "1");
433 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointX/type", '/'), "long");
434 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointX/value", '/'), x);
435 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointY/type", '/'), "long");
436 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointY/value", '/'), y);
438 std::stringstream aStream;
439 boost::property_tree::write_json(aStream, aTree);
441 LOKPostCommand (pDocView, ".uno:ExecuteSearch", aStream.str().c_str(), false);
444 static bool
445 isEmptyRectangle(const GdkRectangle& rRectangle)
447 return rRectangle.x == 0 && rRectangle.y == 0 && rRectangle.width == 0 && rRectangle.height == 0;
450 /// if handled, returns TRUE else FALSE
451 static bool
452 handleTextSelectionOnButtonPress(GdkRectangle& aClick, LOKDocView* pDocView) {
453 LOKDocViewPrivate& priv = getPrivate(pDocView);
455 if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleStartRect, nullptr))
457 g_info("LOKDocView_Impl::signalButton: start of drag start handle");
458 priv->m_bInDragStartHandle = true;
459 return true;
461 else if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleMiddleRect, nullptr))
463 g_info("LOKDocView_Impl::signalButton: start of drag middle handle");
464 priv->m_bInDragMiddleHandle = true;
465 return true;
467 else if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleEndRect, nullptr))
469 g_info("LOKDocView_Impl::signalButton: start of drag end handle");
470 priv->m_bInDragEndHandle = true;
471 return true;
474 return false;
477 /// if handled, returns TRUE else FALSE
478 static bool
479 handleGraphicSelectionOnButtonPress(GdkRectangle& aClick, LOKDocView* pDocView) {
480 LOKDocViewPrivate& priv = getPrivate(pDocView);
481 GError* error = nullptr;
483 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
485 if (gdk_rectangle_intersect(&aClick, &priv->m_aGraphicHandleRects[i], nullptr))
487 g_info("LOKDocView_Impl::signalButton: start of drag graphic handle #%d", i);
488 priv->m_bInDragGraphicHandles[i] = true;
490 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
491 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
492 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_START;
493 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(priv->m_aGraphicHandleRects[i].x + priv->m_aGraphicHandleRects[i].width / 2, priv->m_fZoom);
494 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(priv->m_aGraphicHandleRects[i].y + priv->m_aGraphicHandleRects[i].height / 2, priv->m_fZoom);
495 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
497 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
498 if (error != nullptr)
500 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
501 g_clear_error(&error);
503 g_object_unref(task);
505 return true;
509 return false;
512 /// if handled, returns TRUE else FALSE
513 static bool
514 handleTextSelectionOnButtonRelease(LOKDocView* pDocView) {
515 LOKDocViewPrivate& priv = getPrivate(pDocView);
517 if (priv->m_bInDragStartHandle)
519 g_info("LOKDocView_Impl::signalButton: end of drag start handle");
520 priv->m_bInDragStartHandle = false;
521 return true;
523 else if (priv->m_bInDragMiddleHandle)
525 g_info("LOKDocView_Impl::signalButton: end of drag middle handle");
526 priv->m_bInDragMiddleHandle = false;
527 return true;
529 else if (priv->m_bInDragEndHandle)
531 g_info("LOKDocView_Impl::signalButton: end of drag end handle");
532 priv->m_bInDragEndHandle = false;
533 return true;
536 return false;
539 /// if handled, returns TRUE else FALSE
540 static bool
541 handleGraphicSelectionOnButtonRelease(LOKDocView* pDocView, GdkEventButton* pEvent) {
542 LOKDocViewPrivate& priv = getPrivate(pDocView);
543 GError* error = nullptr;
545 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
547 if (priv->m_bInDragGraphicHandles[i])
549 g_info("LOKDocView_Impl::signalButton: end of drag graphic handle #%d", i);
550 priv->m_bInDragGraphicHandles[i] = false;
552 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
553 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
554 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_END;
555 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(pEvent->x, priv->m_fZoom);
556 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(pEvent->y, priv->m_fZoom);
557 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
559 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
560 if (error != nullptr)
562 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
563 g_clear_error(&error);
565 g_object_unref(task);
567 return true;
571 if (priv->m_bInDragGraphicSelection)
573 g_info("LOKDocView_Impl::signalButton: end of drag graphic selection");
574 priv->m_bInDragGraphicSelection = false;
576 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
577 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
578 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_END;
579 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(pEvent->x, priv->m_fZoom);
580 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(pEvent->y, priv->m_fZoom);
581 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
583 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
584 if (error != nullptr)
586 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
587 g_clear_error(&error);
589 g_object_unref(task);
591 return true;
594 return false;
597 static void
598 postKeyEventInThread(gpointer data)
600 GTask* task = G_TASK(data);
601 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
602 LOKDocViewPrivate& priv = getPrivate(pDocView);
603 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
604 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
605 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
607 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
608 setDocumentView(priv->m_pDocument, priv->m_nViewId);
609 std::stringstream ss;
611 if (priv->m_nTileSizeTwips)
613 ss.str(std::string());
614 ss << "lok::Document::setClientZoom(" << nTileSizePixelsScaled << ", " << nTileSizePixelsScaled << ", " << priv->m_nTileSizeTwips << ", " << priv->m_nTileSizeTwips << ")";
615 g_info("%s", ss.str().c_str());
616 priv->m_pDocument->pClass->setClientZoom(priv->m_pDocument,
617 nTileSizePixelsScaled,
618 nTileSizePixelsScaled,
619 priv->m_nTileSizeTwips,
620 priv->m_nTileSizeTwips);
621 priv->m_nTileSizeTwips = 0;
623 if (priv->m_bVisibleAreaSet)
625 ss.str(std::string());
626 ss << "lok::Document::setClientVisibleArea(" << priv->m_aVisibleArea.x << ", " << priv->m_aVisibleArea.y << ", ";
627 ss << priv->m_aVisibleArea.width << ", " << priv->m_aVisibleArea.height << ")";
628 g_info("%s", ss.str().c_str());
629 priv->m_pDocument->pClass->setClientVisibleArea(priv->m_pDocument,
630 priv->m_aVisibleArea.x,
631 priv->m_aVisibleArea.y,
632 priv->m_aVisibleArea.width,
633 priv->m_aVisibleArea.height);
634 priv->m_bVisibleAreaSet = false;
637 ss.str(std::string());
638 ss << "lok::Document::postKeyEvent(" << pLOEvent->m_nKeyEvent << ", " << pLOEvent->m_nCharCode << ", " << pLOEvent->m_nKeyCode << ")";
639 g_info("%s", ss.str().c_str());
640 priv->m_pDocument->pClass->postKeyEvent(priv->m_pDocument,
641 pLOEvent->m_nKeyEvent,
642 pLOEvent->m_nCharCode,
643 pLOEvent->m_nKeyCode);
646 static gboolean
647 signalKey (GtkWidget* pWidget, GdkEventKey* pEvent)
649 LOKDocView* pDocView = LOK_DOC_VIEW(pWidget);
650 LOKDocViewPrivate& priv = getPrivate(pDocView);
651 int nCharCode = 0;
652 int nKeyCode = 0;
653 GError* error = nullptr;
655 if (!priv->m_bEdit)
657 g_info("signalKey: not in edit mode, ignore");
658 return FALSE;
661 priv->m_nKeyModifier &= KEY_MOD2;
662 switch (pEvent->keyval)
664 case GDK_KEY_BackSpace:
665 nKeyCode = com::sun::star::awt::Key::BACKSPACE;
666 break;
667 case GDK_KEY_Delete:
668 nKeyCode = com::sun::star::awt::Key::DELETE;
669 break;
670 case GDK_KEY_Return:
671 case GDK_KEY_KP_Enter:
672 nKeyCode = com::sun::star::awt::Key::RETURN;
673 break;
674 case GDK_KEY_Escape:
675 nKeyCode = com::sun::star::awt::Key::ESCAPE;
676 break;
677 case GDK_KEY_Tab:
678 nKeyCode = com::sun::star::awt::Key::TAB;
679 break;
680 case GDK_KEY_Down:
681 nKeyCode = com::sun::star::awt::Key::DOWN;
682 break;
683 case GDK_KEY_Up:
684 nKeyCode = com::sun::star::awt::Key::UP;
685 break;
686 case GDK_KEY_Left:
687 nKeyCode = com::sun::star::awt::Key::LEFT;
688 break;
689 case GDK_KEY_Right:
690 nKeyCode = com::sun::star::awt::Key::RIGHT;
691 break;
692 case GDK_KEY_Page_Down:
693 nKeyCode = com::sun::star::awt::Key::PAGEDOWN;
694 break;
695 case GDK_KEY_Page_Up:
696 nKeyCode = com::sun::star::awt::Key::PAGEUP;
697 break;
698 case GDK_KEY_Insert:
699 nKeyCode = com::sun::star::awt::Key::INSERT;
700 break;
701 case GDK_KEY_Shift_L:
702 case GDK_KEY_Shift_R:
703 if (pEvent->type == GDK_KEY_PRESS)
704 priv->m_nKeyModifier |= KEY_SHIFT;
705 break;
706 case GDK_KEY_Control_L:
707 case GDK_KEY_Control_R:
708 if (pEvent->type == GDK_KEY_PRESS)
709 priv->m_nKeyModifier |= KEY_MOD1;
710 break;
711 case GDK_KEY_Alt_L:
712 case GDK_KEY_Alt_R:
713 if (pEvent->type == GDK_KEY_PRESS)
714 priv->m_nKeyModifier |= KEY_MOD2;
715 else
716 priv->m_nKeyModifier &= ~KEY_MOD2;
717 break;
718 default:
719 if (pEvent->keyval >= GDK_KEY_F1 && pEvent->keyval <= GDK_KEY_F26)
720 nKeyCode = com::sun::star::awt::Key::F1 + (pEvent->keyval - GDK_KEY_F1);
721 else
722 nCharCode = gdk_keyval_to_unicode(pEvent->keyval);
725 // rsc is not public API, but should be good enough for debugging purposes.
726 // If this is needed for real, then probably a new param of type
727 // css::awt::KeyModifier is needed in postKeyEvent().
728 if (pEvent->state & GDK_SHIFT_MASK)
729 nKeyCode |= KEY_SHIFT;
731 if (pEvent->state & GDK_CONTROL_MASK)
732 nKeyCode |= KEY_MOD1;
734 if (priv->m_nKeyModifier & KEY_MOD2)
735 nKeyCode |= KEY_MOD2;
737 if (nKeyCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2)) {
738 if (pEvent->keyval >= GDK_KEY_a && pEvent->keyval <= GDK_KEY_z)
740 nKeyCode |= 512 + (pEvent->keyval - GDK_KEY_a);
742 else if (pEvent->keyval >= GDK_KEY_A && pEvent->keyval <= GDK_KEY_Z) {
743 nKeyCode |= 512 + (pEvent->keyval - GDK_KEY_A);
745 else if (pEvent->keyval >= GDK_KEY_0 && pEvent->keyval <= GDK_KEY_9) {
746 nKeyCode |= 256 + (pEvent->keyval - GDK_KEY_0);
750 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
751 LOEvent* pLOEvent = new LOEvent(LOK_POST_KEY);
752 pLOEvent->m_nKeyEvent = pEvent->type == GDK_KEY_RELEASE ? LOK_KEYEVENT_KEYUP : LOK_KEYEVENT_KEYINPUT;
753 pLOEvent->m_nCharCode = nCharCode;
754 pLOEvent->m_nKeyCode = nKeyCode;
755 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
756 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
757 if (error != nullptr)
759 g_warning("Unable to call LOK_POST_KEY: %s", error->message);
760 g_clear_error(&error);
762 g_object_unref(task);
764 return FALSE;
767 static gboolean
768 handleTimeout (gpointer pData)
770 LOKDocView* pDocView = LOK_DOC_VIEW (pData);
771 LOKDocViewPrivate& priv = getPrivate(pDocView);
773 if (priv->m_bEdit)
775 if (priv->m_bCursorOverlayVisible)
776 priv->m_bCursorOverlayVisible = false;
777 else
778 priv->m_bCursorOverlayVisible = true;
779 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
782 return G_SOURCE_CONTINUE;
785 static void
786 commandChanged(LOKDocView* pDocView, const std::string& rString)
788 g_signal_emit(pDocView, doc_view_signals[COMMAND_CHANGED], 0, rString.c_str());
791 static void
792 searchNotFound(LOKDocView* pDocView, const std::string& rString)
794 g_signal_emit(pDocView, doc_view_signals[SEARCH_NOT_FOUND], 0, rString.c_str());
797 static void searchResultCount(LOKDocView* pDocView, const std::string& rString)
799 g_signal_emit(pDocView, doc_view_signals[SEARCH_RESULT_COUNT], 0, rString.c_str());
802 static void commandResult(LOKDocView* pDocView, const std::string& rString)
804 g_signal_emit(pDocView, doc_view_signals[COMMAND_RESULT], 0, rString.c_str());
807 static void addressChanged(LOKDocView* pDocView, const std::string& rString)
809 g_signal_emit(pDocView, doc_view_signals[ADDRESS_CHANGED], 0, rString.c_str());
812 static void formulaChanged(LOKDocView* pDocView, const std::string& rString)
814 g_signal_emit(pDocView, doc_view_signals[FORMULA_CHANGED], 0, rString.c_str());
817 static void reportError(LOKDocView* /*pDocView*/, const std::string& rString)
819 GtkWidget *dialog = gtk_message_dialog_new(nullptr,
820 GTK_DIALOG_DESTROY_WITH_PARENT,
821 GTK_MESSAGE_ERROR,
822 GTK_BUTTONS_CLOSE,
823 "%s",
824 rString.c_str());
825 gtk_dialog_run(GTK_DIALOG(dialog));
826 gtk_widget_destroy(dialog);
829 static void
830 setPart(LOKDocView* pDocView, const std::string& rString)
832 LOKDocViewPrivate& priv = getPrivate(pDocView);
833 priv->m_nPartId = std::stoi(rString);
834 g_signal_emit(pDocView, doc_view_signals[PART_CHANGED], 0, priv->m_nPartId);
837 static void
838 hyperlinkClicked(LOKDocView* pDocView, const std::string& rString)
840 g_signal_emit(pDocView, doc_view_signals[HYPERLINK_CLICKED], 0, rString.c_str());
843 /// Trigger a redraw, invoked on the main thread by other functions running in a thread.
844 static gboolean queueDraw(gpointer pData)
846 GtkWidget* pWidget = static_cast<GtkWidget*>(pData);
848 gtk_widget_queue_draw(pWidget);
850 return G_SOURCE_REMOVE;
853 /// Looks up the author string from initializeForRendering()'s rendering arguments.
854 static std::string getAuthorRenderingArgument(LOKDocViewPrivate& priv)
856 std::stringstream aStream;
857 aStream << priv->m_aRenderingArguments;
858 boost::property_tree::ptree aTree;
859 boost::property_tree::read_json(aStream, aTree);
860 std::string aRet;
861 for (const auto& rPair : aTree)
863 if (rPair.first == ".uno:Author")
865 aRet = rPair.second.get<std::string>("value");
866 break;
869 return aRet;
872 /// Author string <-> View ID map
873 static std::map<std::string, int> g_aAuthorViews;
875 static void refreshSize(LOKDocView* pDocView)
877 LOKDocViewPrivate& priv = getPrivate(pDocView);
879 priv->m_pDocument->pClass->getDocumentSize(priv->m_pDocument, &priv->m_nDocumentWidthTwips, &priv->m_nDocumentHeightTwips);
880 float zoom = priv->m_fZoom;
881 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
882 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
883 long nDocumentWidthTwips = priv->m_nDocumentWidthTwips;
884 long nDocumentHeightTwips = priv->m_nDocumentHeightTwips;
885 long nDocumentWidthPixels = twipToPixel(nDocumentWidthTwips, zoom);
886 long nDocumentHeightPixels = twipToPixel(nDocumentHeightTwips, zoom);
888 // Total number of columns in this document.
889 guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixelsScaled);
890 priv->m_pTileBuffer = std::make_unique<TileBuffer>(nColumns, nScaleFactor);
891 gtk_widget_set_size_request(GTK_WIDGET(pDocView),
892 nDocumentWidthPixels,
893 nDocumentHeightPixels);
896 /// Set up LOKDocView after the document is loaded, invoked on the main thread by openDocumentInThread() running in a thread.
897 static gboolean postDocumentLoad(gpointer pData)
899 LOKDocView* pLOKDocView = static_cast<LOKDocView*>(pData);
900 LOKDocViewPrivate& priv = getPrivate(pLOKDocView);
902 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
903 priv->m_pDocument->pClass->initializeForRendering(priv->m_pDocument, priv->m_aRenderingArguments.c_str());
904 // This returns the view id of the "current" view, but sadly if you load multiple documents that
905 // is apparently not a view showing the most recently loaded document. Not much we can do here,
906 // though. If that is fixed, this comment becomes incorrect.
907 priv->m_nViewId = priv->m_pDocument->pClass->getView(priv->m_pDocument);
908 g_aAuthorViews[getAuthorRenderingArgument(priv)] = priv->m_nViewId;
909 priv->m_pDocument->pClass->registerCallback(priv->m_pDocument, callbackWorker, pLOKDocView);
910 priv->m_nParts = priv->m_pDocument->pClass->getParts(priv->m_pDocument);
911 aGuard.unlock();
912 priv->m_nTimeoutId = g_timeout_add(600, handleTimeout, pLOKDocView);
914 refreshSize(pLOKDocView);
916 gtk_widget_set_can_focus(GTK_WIDGET(pLOKDocView), true);
917 gtk_widget_grab_focus(GTK_WIDGET(pLOKDocView));
918 lok_doc_view_set_zoom(pLOKDocView, 1.0);
920 // we are completely loaded
921 priv->m_bInit = true;
922 g_object_notify_by_pspec(G_OBJECT(pLOKDocView), properties[PROP_IS_INITIALIZED]);
924 return G_SOURCE_REMOVE;
927 /// Implementation of the global callback handler, invoked by globalCallback();
928 static gboolean
929 globalCallback (gpointer pData)
931 CallbackData* pCallback = static_cast<CallbackData*>(pData);
932 LOKDocViewPrivate& priv = getPrivate(pCallback->m_pDocView);
933 bool bModify = false;
935 switch (pCallback->m_nType)
937 case LOK_CALLBACK_STATUS_INDICATOR_START:
939 priv->m_nLoadProgress = 0.0;
940 g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, 0.0);
942 break;
943 case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
945 priv->m_nLoadProgress = static_cast<gdouble>(std::stoi(pCallback->m_aPayload)/100.0);
946 g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, priv->m_nLoadProgress);
948 break;
949 case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
951 priv->m_nLoadProgress = 1.0;
952 g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, 1.0);
954 break;
955 case LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY:
956 bModify = true;
957 [[fallthrough]];
958 case LOK_CALLBACK_DOCUMENT_PASSWORD:
960 char const*const pURL(pCallback->m_aPayload.c_str());
961 g_signal_emit (pCallback->m_pDocView, doc_view_signals[PASSWORD_REQUIRED], 0, pURL, bModify);
963 break;
964 case LOK_CALLBACK_ERROR:
966 reportError(pCallback->m_pDocView, pCallback->m_aPayload);
968 break;
969 case LOK_CALLBACK_SIGNATURE_STATUS:
971 // TODO
973 break;
974 default:
975 g_assert(false);
976 break;
978 delete pCallback;
980 return G_SOURCE_REMOVE;
983 static void
984 globalCallbackWorker(int nType, const char* pPayload, void* pData)
986 LOKDocView* pDocView = LOK_DOC_VIEW (pData);
988 CallbackData* pCallback = new CallbackData(nType, pPayload ? pPayload : "(nil)", pDocView);
989 g_info("LOKDocView_Impl::globalCallbackWorkerImpl: %s, '%s'", lokCallbackTypeToString(nType), pPayload);
990 gdk_threads_add_idle(globalCallback, pCallback);
993 static GdkRectangle
994 payloadToRectangle (LOKDocView* pDocView, const char* pPayload)
996 LOKDocViewPrivate& priv = getPrivate(pDocView);
997 GdkRectangle aRet;
998 // x, y, width, height, part number.
999 gchar** ppCoordinates = g_strsplit(pPayload, ", ", 5);
1000 gchar** ppCoordinate = ppCoordinates;
1002 aRet.width = aRet.height = aRet.x = aRet.y = 0;
1004 if (!*ppCoordinate)
1006 g_strfreev(ppCoordinates);
1007 return aRet;
1009 aRet.x = atoi(*ppCoordinate);
1010 if (aRet.x < 0)
1011 aRet.x = 0;
1012 ++ppCoordinate;
1013 if (!*ppCoordinate)
1015 g_strfreev(ppCoordinates);
1016 return aRet;
1018 aRet.y = atoi(*ppCoordinate);
1019 if (aRet.y < 0)
1020 aRet.y = 0;
1021 ++ppCoordinate;
1022 if (!*ppCoordinate)
1024 g_strfreev(ppCoordinates);
1025 return aRet;
1027 long l = atol(*ppCoordinate);
1028 if (l > std::numeric_limits<int>::max())
1029 aRet.width = std::numeric_limits<int>::max();
1030 else
1031 aRet.width = l;
1032 if (aRet.x + aRet.width > priv->m_nDocumentWidthTwips)
1033 aRet.width = priv->m_nDocumentWidthTwips - aRet.x;
1034 ++ppCoordinate;
1035 if (!*ppCoordinate)
1037 g_strfreev(ppCoordinates);
1038 return aRet;
1040 l = atol(*ppCoordinate);
1041 if (l > std::numeric_limits<int>::max())
1042 aRet.height = std::numeric_limits<int>::max();
1043 else
1044 aRet.height = l;
1045 if (aRet.y + aRet.height > priv->m_nDocumentHeightTwips)
1046 aRet.height = priv->m_nDocumentHeightTwips - aRet.y;
1048 g_strfreev(ppCoordinates);
1049 return aRet;
1052 static std::vector<GdkRectangle>
1053 payloadToRectangles(LOKDocView* pDocView, const char* pPayload)
1055 std::vector<GdkRectangle> aRet;
1057 if (g_strcmp0(pPayload, "EMPTY") == 0)
1058 return aRet;
1060 gchar** ppRectangles = g_strsplit(pPayload, "; ", 0);
1061 for (gchar** ppRectangle = ppRectangles; *ppRectangle; ++ppRectangle)
1062 aRet.push_back(payloadToRectangle(pDocView, *ppRectangle));
1063 g_strfreev(ppRectangles);
1065 return aRet;
1069 static void
1070 setTilesInvalid (LOKDocView* pDocView, const GdkRectangle& rRectangle)
1072 LOKDocViewPrivate& priv = getPrivate(pDocView);
1073 GdkRectangle aRectanglePixels;
1074 GdkPoint aStart, aEnd;
1075 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
1076 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
1078 aRectanglePixels.x = twipToPixel(rRectangle.x, priv->m_fZoom) * nScaleFactor;
1079 aRectanglePixels.y = twipToPixel(rRectangle.y, priv->m_fZoom) * nScaleFactor;
1080 aRectanglePixels.width = twipToPixel(rRectangle.width, priv->m_fZoom) * nScaleFactor;
1081 aRectanglePixels.height = twipToPixel(rRectangle.height, priv->m_fZoom) * nScaleFactor;
1083 aStart.x = aRectanglePixels.y / nTileSizePixelsScaled;
1084 aStart.y = aRectanglePixels.x / nTileSizePixelsScaled;
1085 aEnd.x = (aRectanglePixels.y + aRectanglePixels.height + nTileSizePixelsScaled) / nTileSizePixelsScaled;
1086 aEnd.y = (aRectanglePixels.x + aRectanglePixels.width + nTileSizePixelsScaled) / nTileSizePixelsScaled;
1087 for (int i = aStart.x; i < aEnd.x; i++)
1089 for (int j = aStart.y; j < aEnd.y; j++)
1091 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
1092 priv->m_pTileBuffer->setInvalid(i, j, priv->m_fZoom, task, priv->lokThreadPool);
1093 g_object_unref(task);
1098 static gboolean
1099 callback (gpointer pData)
1101 CallbackData* pCallback = static_cast<CallbackData*>(pData);
1102 LOKDocView* pDocView = LOK_DOC_VIEW (pCallback->m_pDocView);
1103 LOKDocViewPrivate& priv = getPrivate(pDocView);
1105 //callback registered before the widget was destroyed.
1106 //Use existence of lokThreadPool as flag it was torn down
1107 if (!priv->lokThreadPool)
1109 delete pCallback;
1110 return G_SOURCE_REMOVE;
1113 switch (static_cast<LibreOfficeKitCallbackType>(pCallback->m_nType))
1115 case LOK_CALLBACK_INVALIDATE_TILES:
1117 if (pCallback->m_aPayload.compare(0, 5, "EMPTY") != 0) // payload doesn't start with "EMPTY"
1119 GdkRectangle aRectangle = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1120 setTilesInvalid(pDocView, aRectangle);
1122 else
1123 priv->m_pTileBuffer->resetAllTiles();
1125 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1127 break;
1128 case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
1131 std::stringstream aStream(pCallback->m_aPayload);
1132 boost::property_tree::ptree aTree;
1133 boost::property_tree::read_json(aStream, aTree);
1134 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1135 int nViewId = aTree.get<int>("viewId");
1137 priv->m_aVisibleCursor = payloadToRectangle(pDocView, rRectangle.c_str());
1138 priv->m_bCursorOverlayVisible = true;
1139 if(nViewId == priv->m_nViewId)
1141 g_signal_emit(pDocView, doc_view_signals[CURSOR_CHANGED], 0,
1142 priv->m_aVisibleCursor.x,
1143 priv->m_aVisibleCursor.y,
1144 priv->m_aVisibleCursor.width,
1145 priv->m_aVisibleCursor.height);
1147 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1149 break;
1150 case LOK_CALLBACK_TEXT_SELECTION:
1152 priv->m_aTextSelectionRectangles = payloadToRectangles(pDocView, pCallback->m_aPayload.c_str());
1153 bool bIsTextSelected = !priv->m_aTextSelectionRectangles.empty();
1154 // In case the selection is empty, then we get no LOK_CALLBACK_TEXT_SELECTION_START/END events.
1155 if (!bIsTextSelected)
1157 memset(&priv->m_aTextSelectionStart, 0, sizeof(priv->m_aTextSelectionStart));
1158 memset(&priv->m_aHandleStartRect, 0, sizeof(priv->m_aHandleStartRect));
1159 memset(&priv->m_aTextSelectionEnd, 0, sizeof(priv->m_aTextSelectionEnd));
1160 memset(&priv->m_aHandleEndRect, 0, sizeof(priv->m_aHandleEndRect));
1162 else
1163 memset(&priv->m_aHandleMiddleRect, 0, sizeof(priv->m_aHandleMiddleRect));
1165 g_signal_emit(pDocView, doc_view_signals[TEXT_SELECTION], 0, bIsTextSelected);
1166 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1168 break;
1169 case LOK_CALLBACK_TEXT_SELECTION_START:
1171 priv->m_aTextSelectionStart = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1173 break;
1174 case LOK_CALLBACK_TEXT_SELECTION_END:
1176 priv->m_aTextSelectionEnd = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1178 break;
1179 case LOK_CALLBACK_CURSOR_VISIBLE:
1181 priv->m_bCursorVisible = pCallback->m_aPayload == "true";
1183 break;
1184 case LOK_CALLBACK_MOUSE_POINTER:
1186 // We do not want the cursor to get changed in view-only mode
1187 if (priv->m_bEdit)
1189 // The gtk docs claim that most css cursors should be supported, however
1190 // on my system at least this is not true and many cursors are unsupported.
1191 // In this case pCursor = null, which results in the default cursor
1192 // being set.
1193 GdkCursor* pCursor = gdk_cursor_new_from_name(gtk_widget_get_display(GTK_WIDGET(pDocView)),
1194 pCallback->m_aPayload.c_str());
1195 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(pDocView)), pCursor);
1198 break;
1199 case LOK_CALLBACK_GRAPHIC_SELECTION:
1201 if (pCallback->m_aPayload != "EMPTY")
1202 priv->m_aGraphicSelection = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1203 else
1204 memset(&priv->m_aGraphicSelection, 0, sizeof(priv->m_aGraphicSelection));
1205 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1207 break;
1208 case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
1210 std::stringstream aStream(pCallback->m_aPayload);
1211 boost::property_tree::ptree aTree;
1212 boost::property_tree::read_json(aStream, aTree);
1213 int nViewId = aTree.get<int>("viewId");
1214 int nPart = aTree.get<int>("part");
1215 const std::string& rRectangle = aTree.get<std::string>("selection");
1216 if (rRectangle != "EMPTY")
1217 priv->m_aGraphicViewSelections[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1218 else
1220 auto it = priv->m_aGraphicViewSelections.find(nViewId);
1221 if (it != priv->m_aGraphicViewSelections.end())
1222 priv->m_aGraphicViewSelections.erase(it);
1224 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1225 break;
1227 break;
1228 case LOK_CALLBACK_CELL_CURSOR:
1230 if (pCallback->m_aPayload != "EMPTY")
1231 priv->m_aCellCursor = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1232 else
1233 memset(&priv->m_aCellCursor, 0, sizeof(priv->m_aCellCursor));
1234 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1236 break;
1237 case LOK_CALLBACK_HYPERLINK_CLICKED:
1239 hyperlinkClicked(pDocView, pCallback->m_aPayload);
1241 break;
1242 case LOK_CALLBACK_STATE_CHANGED:
1244 commandChanged(pDocView, pCallback->m_aPayload);
1246 break;
1247 case LOK_CALLBACK_SEARCH_NOT_FOUND:
1249 searchNotFound(pDocView, pCallback->m_aPayload);
1251 break;
1252 case LOK_CALLBACK_DOCUMENT_SIZE_CHANGED:
1254 refreshSize(pDocView);
1255 g_signal_emit(pDocView, doc_view_signals[SIZE_CHANGED], 0, nullptr);
1257 break;
1258 case LOK_CALLBACK_SET_PART:
1260 setPart(pDocView, pCallback->m_aPayload);
1262 break;
1263 case LOK_CALLBACK_SEARCH_RESULT_SELECTION:
1265 boost::property_tree::ptree aTree;
1266 std::stringstream aStream(pCallback->m_aPayload);
1267 boost::property_tree::read_json(aStream, aTree);
1268 int nCount = aTree.get_child("searchResultSelection").size();
1269 searchResultCount(pDocView, std::to_string(nCount));
1271 break;
1272 case LOK_CALLBACK_UNO_COMMAND_RESULT:
1274 commandResult(pDocView, pCallback->m_aPayload);
1276 break;
1277 case LOK_CALLBACK_CELL_ADDRESS:
1279 addressChanged(pDocView, pCallback->m_aPayload);
1281 break;
1282 case LOK_CALLBACK_CELL_FORMULA:
1284 formulaChanged(pDocView, pCallback->m_aPayload);
1286 break;
1287 case LOK_CALLBACK_ERROR:
1289 reportError(pDocView, pCallback->m_aPayload);
1291 break;
1292 case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
1294 std::stringstream aStream(pCallback->m_aPayload);
1295 boost::property_tree::ptree aTree;
1296 boost::property_tree::read_json(aStream, aTree);
1297 int nViewId = aTree.get<int>("viewId");
1298 int nPart = aTree.get<int>("part");
1299 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1300 priv->m_aViewCursors[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1301 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1302 break;
1304 case LOK_CALLBACK_TEXT_VIEW_SELECTION:
1306 std::stringstream aStream(pCallback->m_aPayload);
1307 boost::property_tree::ptree aTree;
1308 boost::property_tree::read_json(aStream, aTree);
1309 int nViewId = aTree.get<int>("viewId");
1310 int nPart = aTree.get<int>("part");
1311 const std::string& rSelection = aTree.get<std::string>("selection");
1312 priv->m_aTextViewSelectionRectangles[nViewId] = ViewRectangles(nPart, payloadToRectangles(pDocView, rSelection.c_str()));
1313 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1314 break;
1316 case LOK_CALLBACK_VIEW_CURSOR_VISIBLE:
1318 std::stringstream aStream(pCallback->m_aPayload);
1319 boost::property_tree::ptree aTree;
1320 boost::property_tree::read_json(aStream, aTree);
1321 int nViewId = aTree.get<int>("viewId");
1322 const std::string& rVisible = aTree.get<std::string>("visible");
1323 priv->m_aViewCursorVisibilities[nViewId] = rVisible == "true";
1324 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1325 break;
1327 break;
1328 case LOK_CALLBACK_CELL_VIEW_CURSOR:
1330 std::stringstream aStream(pCallback->m_aPayload);
1331 boost::property_tree::ptree aTree;
1332 boost::property_tree::read_json(aStream, aTree);
1333 int nViewId = aTree.get<int>("viewId");
1334 int nPart = aTree.get<int>("part");
1335 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1336 if (rRectangle != "EMPTY")
1337 priv->m_aCellViewCursors[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1338 else
1340 auto it = priv->m_aCellViewCursors.find(nViewId);
1341 if (it != priv->m_aCellViewCursors.end())
1342 priv->m_aCellViewCursors.erase(it);
1344 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1345 break;
1347 case LOK_CALLBACK_VIEW_LOCK:
1349 std::stringstream aStream(pCallback->m_aPayload);
1350 boost::property_tree::ptree aTree;
1351 boost::property_tree::read_json(aStream, aTree);
1352 int nViewId = aTree.get<int>("viewId");
1353 int nPart = aTree.get<int>("part");
1354 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1355 if (rRectangle != "EMPTY")
1356 priv->m_aViewLockRectangles[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1357 else
1359 auto it = priv->m_aViewLockRectangles.find(nViewId);
1360 if (it != priv->m_aViewLockRectangles.end())
1361 priv->m_aViewLockRectangles.erase(it);
1363 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1364 break;
1366 case LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED:
1368 break;
1370 case LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED:
1372 break;
1374 case LOK_CALLBACK_COMMENT:
1375 g_signal_emit(pCallback->m_pDocView, doc_view_signals[COMMENT], 0, pCallback->m_aPayload.c_str());
1376 break;
1377 case LOK_CALLBACK_RULER_UPDATE:
1378 g_signal_emit(pCallback->m_pDocView, doc_view_signals[RULER], 0, pCallback->m_aPayload.c_str());
1379 break;
1380 case LOK_CALLBACK_WINDOW:
1381 g_signal_emit(pCallback->m_pDocView, doc_view_signals[WINDOW], 0, pCallback->m_aPayload.c_str());
1382 break;
1383 case LOK_CALLBACK_INVALIDATE_HEADER:
1384 g_signal_emit(pCallback->m_pDocView, doc_view_signals[INVALIDATE_HEADER], 0, pCallback->m_aPayload.c_str());
1385 break;
1386 case LOK_CALLBACK_REFERENCE_MARKS:
1388 std::stringstream aStream(pCallback->m_aPayload);
1389 boost::property_tree::ptree aTree;
1390 boost::property_tree::read_json(aStream, aTree);
1392 priv->m_aReferenceMarks.clear();
1394 for(const auto& rMark : aTree.get_child("marks"))
1396 sal_uInt32 nColor = std::stoi(rMark.second.get<std::string>("color"), nullptr, 16);
1397 std::string sRect = rMark.second.get<std::string>("rectangle");
1398 sal_uInt32 nPart = std::stoi(rMark.second.get<std::string>("part"));
1400 GdkRectangle aRect = payloadToRectangle(pDocView, sRect.c_str());
1401 priv->m_aReferenceMarks.push_back(std::pair<ViewRectangle, sal_uInt32>(ViewRectangle(nPart, aRect), nColor));
1404 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1405 break;
1408 case LOK_CALLBACK_CONTENT_CONTROL:
1410 std::stringstream aPayloadStream(pCallback->m_aPayload);
1411 boost::property_tree::ptree aTree;
1412 boost::property_tree::read_json(aPayloadStream, aTree);
1413 auto aAction = aTree.get<std::string>("action");
1414 if (aAction == "show")
1416 auto aRectangles = aTree.get<std::string>("rectangles");
1417 priv->m_aContentControlRectangles = payloadToRectangles(pDocView, aRectangles.c_str());
1419 auto it = aTree.find("alias");
1420 if (it == aTree.not_found())
1422 priv->m_aContentControlAlias.clear();
1424 else
1426 priv->m_aContentControlAlias = it->second.get_value<std::string>();
1429 else if (aAction == "hide")
1431 priv->m_aContentControlRectangles.clear();
1432 priv->m_aContentControlAlias.clear();
1434 else if (aAction == "change-picture")
1436 GtkWidget* pDialog = gtk_file_chooser_dialog_new(
1437 "Open File", GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))),
1438 GTK_FILE_CHOOSER_ACTION_OPEN, "Cancel", GTK_RESPONSE_CANCEL, "Open",
1439 GTK_RESPONSE_ACCEPT, nullptr);
1440 gint nRet = gtk_dialog_run(GTK_DIALOG(pDialog));
1441 if (nRet == GTK_RESPONSE_ACCEPT)
1443 GtkFileChooser* pChooser = GTK_FILE_CHOOSER(pDialog);
1444 char* pFilename = gtk_file_chooser_get_uri(pChooser);
1445 boost::property_tree::ptree aValues;
1446 aValues.put("type", "picture");
1447 aValues.put("changed", pFilename);
1448 std::stringstream aStream;
1449 boost::property_tree::write_json(aStream, aValues);
1450 std::string aJson = aStream.str();
1451 lok_doc_view_send_content_control_event(pDocView, aJson.c_str());
1453 g_free(pFilename);
1455 gtk_widget_destroy(pDialog);
1457 g_signal_emit(pCallback->m_pDocView, doc_view_signals[CONTENT_CONTROL], 0,
1458 pCallback->m_aPayload.c_str());
1459 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1461 break;
1463 case LOK_CALLBACK_STATUS_INDICATOR_START:
1464 case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
1465 case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
1466 case LOK_CALLBACK_DOCUMENT_PASSWORD:
1467 case LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY:
1468 case LOK_CALLBACK_VALIDITY_LIST_BUTTON:
1469 case LOK_CALLBACK_VALIDITY_INPUT_HELP:
1470 case LOK_CALLBACK_SIGNATURE_STATUS:
1471 case LOK_CALLBACK_CONTEXT_MENU:
1472 case LOK_CALLBACK_PROFILE_FRAME:
1473 case LOK_CALLBACK_CLIPBOARD_CHANGED:
1474 case LOK_CALLBACK_CONTEXT_CHANGED:
1475 case LOK_CALLBACK_CELL_SELECTION_AREA:
1476 case LOK_CALLBACK_CELL_AUTO_FILL_AREA:
1477 case LOK_CALLBACK_TABLE_SELECTED:
1478 case LOK_CALLBACK_JSDIALOG:
1479 case LOK_CALLBACK_CALC_FUNCTION_LIST:
1480 case LOK_CALLBACK_TAB_STOP_LIST:
1481 case LOK_CALLBACK_FORM_FIELD_BUTTON:
1482 case LOK_CALLBACK_INVALIDATE_SHEET_GEOMETRY:
1483 case LOK_CALLBACK_DOCUMENT_BACKGROUND_COLOR:
1484 case LOK_COMMAND_BLOCKED:
1485 case LOK_CALLBACK_SC_FOLLOW_JUMP:
1486 case LOK_CALLBACK_PRINT_RANGES:
1487 case LOK_CALLBACK_FONTS_MISSING:
1488 case LOK_CALLBACK_MEDIA_SHAPE:
1489 case LOK_CALLBACK_EXPORT_FILE:
1491 // TODO: Implement me
1492 break;
1495 delete pCallback;
1497 return G_SOURCE_REMOVE;
1500 static void callbackWorker (int nType, const char* pPayload, void* pData)
1502 LOKDocView* pDocView = LOK_DOC_VIEW (pData);
1504 CallbackData* pCallback = new CallbackData(nType, pPayload ? pPayload : "(nil)", pDocView);
1505 LOKDocViewPrivate& priv = getPrivate(pDocView);
1506 std::stringstream ss;
1507 ss << "callbackWorker, view #" << priv->m_nViewId << ": " << lokCallbackTypeToString(nType) << ", '" << (pPayload ? pPayload : "(nil)") << "'";
1508 g_info("%s", ss.str().c_str());
1509 gdk_threads_add_idle(callback, pCallback);
1512 static void
1513 renderHandle(LOKDocView* pDocView,
1514 cairo_t* pCairo,
1515 const GdkRectangle& rCursor,
1516 cairo_surface_t* pHandle,
1517 GdkRectangle& rRectangle)
1519 LOKDocViewPrivate& priv = getPrivate(pDocView);
1520 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
1521 GdkPoint aCursorBottom;
1522 int nHandleWidth, nHandleHeight;
1523 double fHandleScale;
1525 nHandleWidth = cairo_image_surface_get_width(pHandle);
1526 nHandleHeight = cairo_image_surface_get_height(pHandle);
1527 // We want to scale down the handle, so that its height is the same as the cursor caret.
1528 fHandleScale = twipToPixel(rCursor.height, priv->m_fZoom) / nHandleHeight;
1529 // We want the top center of the handle bitmap to be at the bottom center of the cursor rectangle.
1530 aCursorBottom.x = twipToPixel(rCursor.x, priv->m_fZoom) + twipToPixel(rCursor.width, priv->m_fZoom) / 2 - (nHandleWidth * fHandleScale) / 2;
1531 aCursorBottom.y = twipToPixel(rCursor.y, priv->m_fZoom) + twipToPixel(rCursor.height, priv->m_fZoom);
1533 cairo_save (pCairo);
1534 cairo_scale(pCairo, 1.0 / nScaleFactor, 1.0 / nScaleFactor);
1535 cairo_translate(pCairo, aCursorBottom.x * nScaleFactor, aCursorBottom.y * nScaleFactor);
1536 cairo_scale(pCairo, fHandleScale * nScaleFactor, fHandleScale * nScaleFactor);
1537 cairo_set_source_surface(pCairo, pHandle, 0, 0);
1538 cairo_paint(pCairo);
1539 cairo_restore (pCairo);
1541 rRectangle.x = aCursorBottom.x;
1542 rRectangle.y = aCursorBottom.y;
1543 rRectangle.width = nHandleWidth * fHandleScale;
1544 rRectangle.height = nHandleHeight * fHandleScale;
1547 /// Renders handles around an rSelection rectangle on pCairo.
1548 static void
1549 renderGraphicHandle(LOKDocView* pDocView,
1550 cairo_t* pCairo,
1551 const GdkRectangle& rSelection,
1552 const GdkRGBA& rColor)
1554 LOKDocViewPrivate& priv = getPrivate(pDocView);
1555 int nHandleWidth = 9, nHandleHeight = 9;
1556 GdkRectangle aSelection;
1558 aSelection.x = twipToPixel(rSelection.x, priv->m_fZoom);
1559 aSelection.y = twipToPixel(rSelection.y, priv->m_fZoom);
1560 aSelection.width = twipToPixel(rSelection.width, priv->m_fZoom);
1561 aSelection.height = twipToPixel(rSelection.height, priv->m_fZoom);
1563 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
1565 int x = aSelection.x, y = aSelection.y;
1567 switch (i)
1569 case 0: // top-left
1570 break;
1571 case 1: // top-middle
1572 x += aSelection.width / 2;
1573 break;
1574 case 2: // top-right
1575 x += aSelection.width;
1576 break;
1577 case 3: // middle-left
1578 y += aSelection.height / 2;
1579 break;
1580 case 4: // middle-right
1581 x += aSelection.width;
1582 y += aSelection.height / 2;
1583 break;
1584 case 5: // bottom-left
1585 y += aSelection.height;
1586 break;
1587 case 6: // bottom-middle
1588 x += aSelection.width / 2;
1589 y += aSelection.height;
1590 break;
1591 case 7: // bottom-right
1592 x += aSelection.width;
1593 y += aSelection.height;
1594 break;
1597 // Center the handle.
1598 x -= nHandleWidth / 2;
1599 y -= nHandleHeight / 2;
1601 priv->m_aGraphicHandleRects[i].x = x;
1602 priv->m_aGraphicHandleRects[i].y = y;
1603 priv->m_aGraphicHandleRects[i].width = nHandleWidth;
1604 priv->m_aGraphicHandleRects[i].height = nHandleHeight;
1606 cairo_set_source_rgb(pCairo, rColor.red, rColor.green, rColor.blue);
1607 cairo_rectangle(pCairo, x, y, nHandleWidth, nHandleHeight);
1608 cairo_fill(pCairo);
1612 /// Finishes the paint tile operation and returns the result, if any
1613 static gpointer
1614 paintTileFinish(LOKDocView* pDocView, GAsyncResult* res, GError **error)
1616 GTask* task = G_TASK(res);
1618 g_return_val_if_fail(LOK_IS_DOC_VIEW(pDocView), nullptr);
1619 g_return_val_if_fail(g_task_is_valid(res, pDocView), nullptr);
1620 g_return_val_if_fail(error == nullptr || *error == nullptr, nullptr);
1622 return g_task_propagate_pointer(task, error);
1625 /// Callback called in the main UI thread when paintTileInThread in LOK thread has finished
1626 static void
1627 paintTileCallback(GObject* sourceObject, GAsyncResult* res, gpointer userData)
1629 LOKDocView* pDocView = LOK_DOC_VIEW(sourceObject);
1630 LOKDocViewPrivate& priv = getPrivate(pDocView);
1631 LOEvent* pLOEvent = static_cast<LOEvent*>(userData);
1632 std::unique_ptr<TileBuffer>& buffer = priv->m_pTileBuffer;
1633 GError* error;
1635 error = nullptr;
1636 cairo_surface_t* pSurface = static_cast<cairo_surface_t*>(paintTileFinish(pDocView, res, &error));
1637 if (error != nullptr)
1639 if (error->domain == LOK_TILEBUFFER_ERROR &&
1640 error->code == LOK_TILEBUFFER_CHANGED)
1641 g_info("Skipping paint tile request because corresponding"
1642 "tile buffer has been destroyed");
1643 else
1644 g_warning("Unable to get painted GdkPixbuf: %s", error->message);
1645 g_error_free(error);
1646 return;
1649 buffer->setTile(pLOEvent->m_nPaintTileX, pLOEvent->m_nPaintTileY, pSurface);
1650 gdk_threads_add_idle(queueDraw, GTK_WIDGET(pDocView));
1652 cairo_surface_destroy(pSurface);
1656 static bool
1657 renderDocument(LOKDocView* pDocView, cairo_t* pCairo)
1659 LOKDocViewPrivate& priv = getPrivate(pDocView);
1660 GdkRectangle aVisibleArea;
1661 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
1662 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
1663 long nDocumentWidthPixels = twipToPixel(priv->m_nDocumentWidthTwips, priv->m_fZoom) * nScaleFactor;
1664 long nDocumentHeightPixels = twipToPixel(priv->m_nDocumentHeightTwips, priv->m_fZoom) * nScaleFactor;
1665 // Total number of rows / columns in this document.
1666 guint nRows = ceil(static_cast<double>(nDocumentHeightPixels) / nTileSizePixelsScaled);
1667 guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixelsScaled);
1669 cairo_save (pCairo);
1670 cairo_scale (pCairo, 1.0/nScaleFactor, 1.0/nScaleFactor);
1671 gdk_cairo_get_clip_rectangle (pCairo, &aVisibleArea);
1672 aVisibleArea.x = pixelToTwip (aVisibleArea.x, priv->m_fZoom);
1673 aVisibleArea.y = pixelToTwip (aVisibleArea.y, priv->m_fZoom);
1674 aVisibleArea.width = pixelToTwip (aVisibleArea.width, priv->m_fZoom);
1675 aVisibleArea.height = pixelToTwip (aVisibleArea.height, priv->m_fZoom);
1677 // Render the tiles.
1678 for (guint nRow = 0; nRow < nRows; ++nRow)
1680 for (guint nColumn = 0; nColumn < nColumns; ++nColumn)
1682 GdkRectangle aTileRectangleTwips, aTileRectanglePixels;
1683 bool bPaint = true;
1685 // Determine size of the tile: the rightmost/bottommost tiles may
1686 // be smaller, and we need the size to decide if we need to repaint.
1687 if (nColumn == nColumns - 1)
1688 aTileRectanglePixels.width = nDocumentWidthPixels - nColumn * nTileSizePixelsScaled;
1689 else
1690 aTileRectanglePixels.width = nTileSizePixelsScaled;
1691 if (nRow == nRows - 1)
1692 aTileRectanglePixels.height = nDocumentHeightPixels - nRow * nTileSizePixelsScaled;
1693 else
1694 aTileRectanglePixels.height = nTileSizePixelsScaled;
1696 // Determine size and position of the tile in document coordinates,
1697 // so we can decide if we can skip painting for partial rendering.
1698 aTileRectangleTwips.x = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom) * nColumn;
1699 aTileRectangleTwips.y = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom) * nRow;
1700 aTileRectangleTwips.width = pixelToTwip(aTileRectanglePixels.width, priv->m_fZoom);
1701 aTileRectangleTwips.height = pixelToTwip(aTileRectanglePixels.height, priv->m_fZoom);
1703 if (!gdk_rectangle_intersect(&aVisibleArea, &aTileRectangleTwips, nullptr))
1704 bPaint = false;
1706 if (bPaint)
1708 LOEvent* pLOEvent = new LOEvent(LOK_PAINT_TILE);
1709 pLOEvent->m_nPaintTileX = nRow;
1710 pLOEvent->m_nPaintTileY = nColumn;
1711 pLOEvent->m_fPaintTileZoom = priv->m_fZoom;
1712 pLOEvent->m_pTileBuffer = &*priv->m_pTileBuffer;
1713 GTask* task = g_task_new(pDocView, nullptr, paintTileCallback, pLOEvent);
1714 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
1716 Tile& currentTile = priv->m_pTileBuffer->getTile(nRow, nColumn, task, priv->lokThreadPool);
1717 cairo_surface_t* pSurface = currentTile.getBuffer();
1718 cairo_set_source_surface(pCairo, pSurface,
1719 twipToPixel(aTileRectangleTwips.x, priv->m_fZoom),
1720 twipToPixel(aTileRectangleTwips.y, priv->m_fZoom));
1721 cairo_paint(pCairo);
1722 g_object_unref(task);
1727 cairo_restore (pCairo);
1728 return false;
1731 static const GdkRGBA& getDarkColor(int nViewId, LOKDocViewPrivate& priv)
1733 static std::map<int, GdkRGBA> aColorMap;
1734 auto it = aColorMap.find(nViewId);
1735 if (it != aColorMap.end())
1736 return it->second;
1738 if (priv->m_eDocumentType == LOK_DOCTYPE_TEXT)
1740 char* pValues = priv->m_pDocument->pClass->getCommandValues(priv->m_pDocument, ".uno:TrackedChangeAuthors");
1741 std::stringstream aInfo;
1742 aInfo << "lok::Document::getCommandValues('.uno:TrackedChangeAuthors') returned '" << pValues << "'" << std::endl;
1743 g_info("%s", aInfo.str().c_str());
1745 std::stringstream aStream(pValues);
1746 boost::property_tree::ptree aTree;
1747 boost::property_tree::read_json(aStream, aTree);
1748 for (const auto& rValue : aTree.get_child("authors"))
1750 const std::string& rName = rValue.second.get<std::string>("name");
1751 guint32 nColor = rValue.second.get<guint32>("color");
1752 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};
1753 auto itAuthorViews = g_aAuthorViews.find(rName);
1754 if (itAuthorViews != g_aAuthorViews.end())
1755 aColorMap[itAuthorViews->second] = aColor;
1758 else
1760 // Based on tools/color.hxx, COL_AUTHOR1_DARK..COL_AUTHOR9_DARK.
1761 static std::vector<GdkRGBA> aColors =
1763 {(double(198))/255, (double(146))/255, (double(0))/255, 0},
1764 {(double(6))/255, (double(70))/255, (double(162))/255, 0},
1765 {(double(87))/255, (double(157))/255, (double(28))/255, 0},
1766 {(double(105))/255, (double(43))/255, (double(157))/255, 0},
1767 {(double(197))/255, (double(0))/255, (double(11))/255, 0},
1768 {(double(0))/255, (double(128))/255, (double(128))/255, 0},
1769 {(double(140))/255, (double(132))/255, (double(0))/255, 0},
1770 {(double(43))/255, (double(85))/255, (double(107))/255, 0},
1771 {(double(209))/255, (double(118))/255, (double(0))/255, 0},
1773 static int nColorCounter = 0;
1774 GdkRGBA aColor = aColors[nColorCounter++ % aColors.size()];
1775 aColorMap[nViewId] = aColor;
1777 assert(aColorMap.find(nViewId) != aColorMap.end());
1778 return aColorMap[nViewId];
1781 static bool
1782 renderOverlay(LOKDocView* pDocView, cairo_t* pCairo)
1784 LOKDocViewPrivate& priv = getPrivate(pDocView);
1786 if (priv->m_bEdit && priv->m_bCursorVisible && priv->m_bCursorOverlayVisible && !isEmptyRectangle(priv->m_aVisibleCursor))
1788 if (priv->m_aVisibleCursor.width < 30)
1789 // Set a minimal width if it would be 0.
1790 priv->m_aVisibleCursor.width = 30;
1792 cairo_set_source_rgb(pCairo, 0, 0, 0);
1793 cairo_rectangle(pCairo,
1794 twipToPixel(priv->m_aVisibleCursor.x, priv->m_fZoom),
1795 twipToPixel(priv->m_aVisibleCursor.y, priv->m_fZoom),
1796 twipToPixel(priv->m_aVisibleCursor.width, priv->m_fZoom),
1797 twipToPixel(priv->m_aVisibleCursor.height, priv->m_fZoom));
1798 cairo_fill(pCairo);
1801 // View cursors: they do not blink and are colored.
1802 if (priv->m_bEdit && !priv->m_aViewCursors.empty())
1804 for (auto& rPair : priv->m_aViewCursors)
1806 auto itVisibility = priv->m_aViewCursorVisibilities.find(rPair.first);
1807 if (itVisibility != priv->m_aViewCursorVisibilities.end() && !itVisibility->second)
1808 continue;
1810 // Show view cursors when in Writer or when the part matches.
1811 if (rPair.second.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
1812 continue;
1814 GdkRectangle& rCursor = rPair.second.m_aRectangle;
1815 if (rCursor.width < 30)
1816 // Set a minimal width if it would be 0.
1817 rCursor.width = 30;
1819 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1820 cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
1821 cairo_rectangle(pCairo,
1822 twipToPixel(rCursor.x, priv->m_fZoom),
1823 twipToPixel(rCursor.y, priv->m_fZoom),
1824 twipToPixel(rCursor.width, priv->m_fZoom),
1825 twipToPixel(rCursor.height, priv->m_fZoom));
1826 cairo_fill(pCairo);
1830 if (priv->m_bEdit && priv->m_bCursorVisible && !isEmptyRectangle(priv->m_aVisibleCursor) && priv->m_aTextSelectionRectangles.empty())
1832 // Have a cursor, but no selection: we need the middle handle.
1833 gchar* handleMiddlePath = g_strconcat (priv->m_aLOPath.c_str(), CURSOR_HANDLE_DIR, "handle_image_middle.png", nullptr);
1834 if (!priv->m_pHandleMiddle)
1836 priv->m_pHandleMiddle = cairo_image_surface_create_from_png(handleMiddlePath);
1837 assert(cairo_surface_status(priv->m_pHandleMiddle) == CAIRO_STATUS_SUCCESS);
1839 g_free (handleMiddlePath);
1840 renderHandle(pDocView, pCairo, priv->m_aVisibleCursor, priv->m_pHandleMiddle, priv->m_aHandleMiddleRect);
1843 if (!priv->m_aTextSelectionRectangles.empty())
1845 for (const GdkRectangle& rRectangle : priv->m_aTextSelectionRectangles)
1847 // Blue with 75% transparency.
1848 cairo_set_source_rgba(pCairo, (double(0x43))/255, (double(0xac))/255, (double(0xe8))/255, 0.25);
1849 cairo_rectangle(pCairo,
1850 twipToPixel(rRectangle.x, priv->m_fZoom),
1851 twipToPixel(rRectangle.y, priv->m_fZoom),
1852 twipToPixel(rRectangle.width, priv->m_fZoom),
1853 twipToPixel(rRectangle.height, priv->m_fZoom));
1854 cairo_fill(pCairo);
1857 // Handles
1858 if (!isEmptyRectangle(priv->m_aTextSelectionStart))
1860 // Have a start position: we need a start handle.
1861 gchar* handleStartPath = g_strconcat (priv->m_aLOPath.c_str(), CURSOR_HANDLE_DIR, "handle_image_start.png", nullptr);
1862 if (!priv->m_pHandleStart)
1864 priv->m_pHandleStart = cairo_image_surface_create_from_png(handleStartPath);
1865 assert(cairo_surface_status(priv->m_pHandleStart) == CAIRO_STATUS_SUCCESS);
1867 renderHandle(pDocView, pCairo, priv->m_aTextSelectionStart, priv->m_pHandleStart, priv->m_aHandleStartRect);
1868 g_free (handleStartPath);
1870 if (!isEmptyRectangle(priv->m_aTextSelectionEnd))
1872 // Have a start position: we need an end handle.
1873 gchar* handleEndPath = g_strconcat (priv->m_aLOPath.c_str(), CURSOR_HANDLE_DIR, "handle_image_end.png", nullptr);
1874 if (!priv->m_pHandleEnd)
1876 priv->m_pHandleEnd = cairo_image_surface_create_from_png(handleEndPath);
1877 assert(cairo_surface_status(priv->m_pHandleEnd) == CAIRO_STATUS_SUCCESS);
1879 renderHandle(pDocView, pCairo, priv->m_aTextSelectionEnd, priv->m_pHandleEnd, priv->m_aHandleEndRect);
1880 g_free (handleEndPath);
1884 if (!priv->m_aContentControlRectangles.empty())
1886 for (const GdkRectangle& rRectangle : priv->m_aContentControlRectangles)
1888 // Black with 75% transparency.
1889 cairo_set_source_rgba(pCairo, (double(0x7f))/255, (double(0x7f))/255, (double(0x7f))/255, 0.25);
1890 cairo_rectangle(pCairo,
1891 twipToPixel(rRectangle.x, priv->m_fZoom),
1892 twipToPixel(rRectangle.y, priv->m_fZoom),
1893 twipToPixel(rRectangle.width, priv->m_fZoom),
1894 twipToPixel(rRectangle.height, priv->m_fZoom));
1895 cairo_fill(pCairo);
1898 if (!priv->m_aContentControlAlias.empty())
1900 cairo_text_extents_t aExtents;
1901 cairo_text_extents(pCairo, priv->m_aContentControlAlias.c_str(), &aExtents);
1902 // Blue with 75% transparency.
1903 cairo_set_source_rgba(pCairo, 0, 0, 1, 0.25);
1904 cairo_rectangle(pCairo,
1905 twipToPixel(priv->m_aContentControlRectangles[0].x, priv->m_fZoom) + aExtents.x_bearing,
1906 twipToPixel(priv->m_aContentControlRectangles[0].y, priv->m_fZoom) + aExtents.y_bearing,
1907 aExtents.width,
1908 aExtents.height);
1909 cairo_fill(pCairo);
1911 cairo_move_to(pCairo,
1912 twipToPixel(priv->m_aContentControlRectangles[0].x, priv->m_fZoom),
1913 twipToPixel(priv->m_aContentControlRectangles[0].y, priv->m_fZoom));
1914 cairo_set_source_rgb(pCairo, 0, 0, 0);
1915 cairo_show_text(pCairo, priv->m_aContentControlAlias.c_str());
1916 cairo_fill(pCairo);
1920 // Selections of other views.
1921 for (const auto& rPair : priv->m_aTextViewSelectionRectangles)
1923 if (rPair.second.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
1924 continue;
1926 for (const GdkRectangle& rRectangle : rPair.second.m_aRectangles)
1928 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1929 // 75% transparency.
1930 cairo_set_source_rgba(pCairo, rDark.red, rDark.green, rDark.blue, 0.25);
1931 cairo_rectangle(pCairo,
1932 twipToPixel(rRectangle.x, priv->m_fZoom),
1933 twipToPixel(rRectangle.y, priv->m_fZoom),
1934 twipToPixel(rRectangle.width, priv->m_fZoom),
1935 twipToPixel(rRectangle.height, priv->m_fZoom));
1936 cairo_fill(pCairo);
1940 if (!isEmptyRectangle(priv->m_aGraphicSelection))
1942 GdkRGBA const aBlack{0, 0, 0, 0};
1943 renderGraphicHandle(pDocView, pCairo, priv->m_aGraphicSelection, aBlack);
1946 // Graphic selections of other views.
1947 for (const auto& rPair : priv->m_aGraphicViewSelections)
1949 const ViewRectangle& rRectangle = rPair.second;
1950 if (rRectangle.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
1951 continue;
1953 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1954 renderGraphicHandle(pDocView, pCairo, rRectangle.m_aRectangle, rDark);
1957 // Draw the cell cursor.
1958 if (!isEmptyRectangle(priv->m_aCellCursor))
1960 cairo_set_source_rgb(pCairo, 0, 0, 0);
1961 cairo_rectangle(pCairo,
1962 twipToPixel(priv->m_aCellCursor.x, priv->m_fZoom),
1963 twipToPixel(priv->m_aCellCursor.y, priv->m_fZoom),
1964 twipToPixel(priv->m_aCellCursor.width, priv->m_fZoom),
1965 twipToPixel(priv->m_aCellCursor.height, priv->m_fZoom));
1966 cairo_set_line_width(pCairo, 2.0);
1967 cairo_stroke(pCairo);
1970 // Cell view cursors: they are colored.
1971 for (const auto& rPair : priv->m_aCellViewCursors)
1973 const ViewRectangle& rCursor = rPair.second;
1974 if (rCursor.m_nPart != priv->m_nPartId)
1975 continue;
1977 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1978 cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
1979 cairo_rectangle(pCairo,
1980 twipToPixel(rCursor.m_aRectangle.x, priv->m_fZoom),
1981 twipToPixel(rCursor.m_aRectangle.y, priv->m_fZoom),
1982 twipToPixel(rCursor.m_aRectangle.width, priv->m_fZoom),
1983 twipToPixel(rCursor.m_aRectangle.height, priv->m_fZoom));
1984 cairo_set_line_width(pCairo, 2.0);
1985 cairo_stroke(pCairo);
1988 // Draw reference marks.
1989 for (const auto& rPair : priv->m_aReferenceMarks)
1991 const ViewRectangle& rMark = rPair.first;
1992 if (rMark.m_nPart != priv->m_nPartId)
1993 continue;
1995 sal_uInt32 nColor = rPair.second;
1996 sal_uInt8 nRed = (nColor >> 16) & 0xff;
1997 sal_uInt8 nGreen = (nColor >> 8) & 0xff;
1998 sal_uInt8 nBlue = nColor & 0xff;
1999 cairo_set_source_rgb(pCairo, nRed, nGreen, nBlue);
2000 cairo_rectangle(pCairo,
2001 twipToPixel(rMark.m_aRectangle.x, priv->m_fZoom),
2002 twipToPixel(rMark.m_aRectangle.y, priv->m_fZoom),
2003 twipToPixel(rMark.m_aRectangle.width, priv->m_fZoom),
2004 twipToPixel(rMark.m_aRectangle.height, priv->m_fZoom));
2005 cairo_set_line_width(pCairo, 2.0);
2006 cairo_stroke(pCairo);
2009 // View locks: they are colored.
2010 for (const auto& rPair : priv->m_aViewLockRectangles)
2012 const ViewRectangle& rRectangle = rPair.second;
2013 if (rRectangle.m_nPart != priv->m_nPartId)
2014 continue;
2016 // Draw a rectangle.
2017 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
2018 cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
2019 cairo_rectangle(pCairo,
2020 twipToPixel(rRectangle.m_aRectangle.x, priv->m_fZoom),
2021 twipToPixel(rRectangle.m_aRectangle.y, priv->m_fZoom),
2022 twipToPixel(rRectangle.m_aRectangle.width, priv->m_fZoom),
2023 twipToPixel(rRectangle.m_aRectangle.height, priv->m_fZoom));
2024 cairo_set_line_width(pCairo, 2.0);
2025 cairo_stroke(pCairo);
2027 // And a lock.
2028 cairo_rectangle(pCairo,
2029 twipToPixel(rRectangle.m_aRectangle.x + rRectangle.m_aRectangle.width, priv->m_fZoom) - 25,
2030 twipToPixel(rRectangle.m_aRectangle.y + rRectangle.m_aRectangle.height, priv->m_fZoom) - 15,
2032 10);
2033 cairo_fill(pCairo);
2034 cairo_arc(pCairo,
2035 twipToPixel(rRectangle.m_aRectangle.x + rRectangle.m_aRectangle.width, priv->m_fZoom) - 15,
2036 twipToPixel(rRectangle.m_aRectangle.y + rRectangle.m_aRectangle.height, priv->m_fZoom) - 15,
2038 M_PI,
2039 2 * M_PI);
2040 cairo_stroke(pCairo);
2043 return false;
2046 static gboolean
2047 lok_doc_view_signal_button(GtkWidget* pWidget, GdkEventButton* pEvent)
2049 LOKDocView* pDocView = LOK_DOC_VIEW (pWidget);
2050 LOKDocViewPrivate& priv = getPrivate(pDocView);
2051 GError* error = nullptr;
2053 g_info("LOKDocView_Impl::signalButton: %d, %d (in twips: %d, %d)",
2054 static_cast<int>(pEvent->x), static_cast<int>(pEvent->y),
2055 static_cast<int>(pixelToTwip(pEvent->x, priv->m_fZoom)),
2056 static_cast<int>(pixelToTwip(pEvent->y, priv->m_fZoom)));
2057 gtk_widget_grab_focus(GTK_WIDGET(pDocView));
2059 switch (pEvent->type)
2061 case GDK_BUTTON_PRESS:
2063 GdkRectangle aClick;
2064 aClick.x = pEvent->x;
2065 aClick.y = pEvent->y;
2066 aClick.width = 1;
2067 aClick.height = 1;
2069 if (handleTextSelectionOnButtonPress(aClick, pDocView))
2070 return FALSE;
2071 if (handleGraphicSelectionOnButtonPress(aClick, pDocView))
2072 return FALSE;
2074 int nCount = 1;
2075 if ((pEvent->time - priv->m_nLastButtonPressTime) < 250)
2076 nCount++;
2077 priv->m_nLastButtonPressTime = pEvent->time;
2078 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2079 LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
2080 pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEBUTTONDOWN;
2081 pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
2082 pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
2083 pLOEvent->m_nPostMouseEventCount = nCount;
2084 switch (pEvent->button)
2086 case 1:
2087 pLOEvent->m_nPostMouseEventButton = MOUSE_LEFT;
2088 break;
2089 case 2:
2090 pLOEvent->m_nPostMouseEventButton = MOUSE_MIDDLE;
2091 break;
2092 case 3:
2093 pLOEvent->m_nPostMouseEventButton = MOUSE_RIGHT;
2094 break;
2096 pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
2097 priv->m_nLastButtonPressed = pLOEvent->m_nPostMouseEventButton;
2098 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2100 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2101 if (error != nullptr)
2103 g_warning("Unable to call LOK_POST_MOUSE_EVENT: %s", error->message);
2104 g_clear_error(&error);
2106 g_object_unref(task);
2107 break;
2109 case GDK_BUTTON_RELEASE:
2111 if (handleTextSelectionOnButtonRelease(pDocView))
2112 return FALSE;
2113 if (handleGraphicSelectionOnButtonRelease(pDocView, pEvent))
2114 return FALSE;
2116 int nCount = 1;
2117 if ((pEvent->time - priv->m_nLastButtonReleaseTime) < 250)
2118 nCount++;
2119 priv->m_nLastButtonReleaseTime = pEvent->time;
2120 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2121 LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
2122 pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEBUTTONUP;
2123 pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
2124 pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
2125 pLOEvent->m_nPostMouseEventCount = nCount;
2126 switch (pEvent->button)
2128 case 1:
2129 pLOEvent->m_nPostMouseEventButton = MOUSE_LEFT;
2130 break;
2131 case 2:
2132 pLOEvent->m_nPostMouseEventButton = MOUSE_MIDDLE;
2133 break;
2134 case 3:
2135 pLOEvent->m_nPostMouseEventButton = MOUSE_RIGHT;
2136 break;
2138 pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
2139 priv->m_nLastButtonPressed = pLOEvent->m_nPostMouseEventButton;
2140 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2142 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2143 if (error != nullptr)
2145 g_warning("Unable to call LOK_POST_MOUSE_EVENT: %s", error->message);
2146 g_clear_error(&error);
2148 g_object_unref(task);
2149 break;
2151 default:
2152 break;
2154 return FALSE;
2157 static void
2158 getDragPoint(GdkRectangle* pHandle,
2159 GdkEventMotion* pEvent,
2160 GdkPoint* pPoint)
2162 GdkPoint aCursor, aHandle;
2164 // Center of the cursor rectangle: we know that it's above the handle.
2165 aCursor.x = pHandle->x + pHandle->width / 2;
2166 aCursor.y = pHandle->y - pHandle->height / 2;
2167 // Center of the handle rectangle.
2168 aHandle.x = pHandle->x + pHandle->width / 2;
2169 aHandle.y = pHandle->y + pHandle->height / 2;
2170 // Our target is the original cursor position + the dragged offset.
2171 pPoint->x = aCursor.x + (pEvent->x - aHandle.x);
2172 pPoint->y = aCursor.y + (pEvent->y - aHandle.y);
2175 static gboolean
2176 lok_doc_view_signal_motion (GtkWidget* pWidget, GdkEventMotion* pEvent)
2178 LOKDocView* pDocView = LOK_DOC_VIEW (pWidget);
2179 LOKDocViewPrivate& priv = getPrivate(pDocView);
2180 GdkPoint aPoint;
2181 GError* error = nullptr;
2183 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2184 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2185 if (priv->m_bInDragMiddleHandle)
2187 g_info("lcl_signalMotion: dragging the middle handle");
2188 getDragPoint(&priv->m_aHandleMiddleRect, pEvent, &aPoint);
2189 priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_RESET, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
2190 return FALSE;
2192 if (priv->m_bInDragStartHandle)
2194 g_info("lcl_signalMotion: dragging the start handle");
2195 getDragPoint(&priv->m_aHandleStartRect, pEvent, &aPoint);
2196 priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_START, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
2197 return FALSE;
2199 if (priv->m_bInDragEndHandle)
2201 g_info("lcl_signalMotion: dragging the end handle");
2202 getDragPoint(&priv->m_aHandleEndRect, pEvent, &aPoint);
2203 priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_END, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
2204 return FALSE;
2206 aGuard.unlock();
2207 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
2209 if (priv->m_bInDragGraphicHandles[i])
2211 g_info("lcl_signalMotion: dragging the graphic handle #%d", i);
2212 return FALSE;
2215 if (priv->m_bInDragGraphicSelection)
2217 g_info("lcl_signalMotion: dragging the graphic selection");
2218 return FALSE;
2221 GdkRectangle aMotionInTwipsInTwips;
2222 aMotionInTwipsInTwips.x = pixelToTwip(pEvent->x, priv->m_fZoom);
2223 aMotionInTwipsInTwips.y = pixelToTwip(pEvent->y, priv->m_fZoom);
2224 aMotionInTwipsInTwips.width = 1;
2225 aMotionInTwipsInTwips.height = 1;
2226 if (gdk_rectangle_intersect(&aMotionInTwipsInTwips, &priv->m_aGraphicSelection, nullptr))
2228 g_info("lcl_signalMotion: start of drag graphic selection");
2229 priv->m_bInDragGraphicSelection = true;
2231 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2232 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
2233 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_START;
2234 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(pEvent->x, priv->m_fZoom);
2235 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(pEvent->y, priv->m_fZoom);
2236 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2238 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2239 if (error != nullptr)
2241 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
2242 g_clear_error(&error);
2244 g_object_unref(task);
2246 return FALSE;
2249 // Otherwise a mouse move, as on the desktop.
2251 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2252 LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
2253 pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEMOVE;
2254 pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
2255 pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
2256 pLOEvent->m_nPostMouseEventCount = 1;
2257 pLOEvent->m_nPostMouseEventButton = priv->m_nLastButtonPressed;
2258 pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
2260 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2262 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2263 if (error != nullptr)
2265 g_warning("Unable to call LOK_MOUSEEVENT_MOUSEMOVE: %s", error->message);
2266 g_clear_error(&error);
2268 g_object_unref(task);
2270 return FALSE;
2273 static void
2274 setGraphicSelectionInThread(gpointer data)
2276 GTask* task = G_TASK(data);
2277 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2278 LOKDocViewPrivate& priv = getPrivate(pDocView);
2279 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2281 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2282 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2283 std::stringstream ss;
2284 ss << "lok::Document::setGraphicSelection(" << pLOEvent->m_nSetGraphicSelectionType;
2285 ss << ", " << pLOEvent->m_nSetGraphicSelectionX;
2286 ss << ", " << pLOEvent->m_nSetGraphicSelectionY << ")";
2287 g_info("%s", ss.str().c_str());
2288 priv->m_pDocument->pClass->setGraphicSelection(priv->m_pDocument,
2289 pLOEvent->m_nSetGraphicSelectionType,
2290 pLOEvent->m_nSetGraphicSelectionX,
2291 pLOEvent->m_nSetGraphicSelectionY);
2294 static void
2295 setClientZoomInThread(gpointer data)
2297 GTask* task = G_TASK(data);
2298 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2299 LOKDocViewPrivate& priv = getPrivate(pDocView);
2300 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2302 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2303 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2304 priv->m_pDocument->pClass->setClientZoom(priv->m_pDocument,
2305 pLOEvent->m_nTilePixelWidth,
2306 pLOEvent->m_nTilePixelHeight,
2307 pLOEvent->m_nTileTwipWidth,
2308 pLOEvent->m_nTileTwipHeight);
2311 static void
2312 postMouseEventInThread(gpointer data)
2314 GTask* task = G_TASK(data);
2315 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2316 LOKDocViewPrivate& priv = getPrivate(pDocView);
2317 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2319 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2320 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2321 std::stringstream ss;
2322 ss << "lok::Document::postMouseEvent(" << pLOEvent->m_nPostMouseEventType;
2323 ss << ", " << pLOEvent->m_nPostMouseEventX;
2324 ss << ", " << pLOEvent->m_nPostMouseEventY;
2325 ss << ", " << pLOEvent->m_nPostMouseEventCount;
2326 ss << ", " << pLOEvent->m_nPostMouseEventButton;
2327 ss << ", " << pLOEvent->m_nPostMouseEventModifier << ")";
2328 g_info("%s", ss.str().c_str());
2329 priv->m_pDocument->pClass->postMouseEvent(priv->m_pDocument,
2330 pLOEvent->m_nPostMouseEventType,
2331 pLOEvent->m_nPostMouseEventX,
2332 pLOEvent->m_nPostMouseEventY,
2333 pLOEvent->m_nPostMouseEventCount,
2334 pLOEvent->m_nPostMouseEventButton,
2335 pLOEvent->m_nPostMouseEventModifier);
2338 static void
2339 openDocumentInThread (gpointer data)
2341 GTask* task = G_TASK(data);
2342 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2343 LOKDocViewPrivate& priv = getPrivate(pDocView);
2345 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2346 if ( priv->m_pDocument )
2348 priv->m_pDocument->pClass->destroy( priv->m_pDocument );
2349 priv->m_pDocument = nullptr;
2352 priv->m_pOffice->pClass->registerCallback(priv->m_pOffice, globalCallbackWorker, pDocView);
2353 std::string url = priv->m_aDocPath;
2354 if (gchar* pURL = g_filename_to_uri(url.c_str(), nullptr, nullptr))
2356 url = pURL;
2357 g_free(pURL);
2359 priv->m_pDocument = priv->m_pOffice->pClass->documentLoadWithOptions( priv->m_pOffice, url.c_str(), "en-US" );
2360 if ( !priv->m_pDocument )
2362 char *pError = priv->m_pOffice->pClass->getError( priv->m_pOffice );
2363 g_task_return_new_error(task, g_quark_from_static_string ("LOK error"), 0, "%s", pError);
2365 else
2367 priv->m_eDocumentType = static_cast<LibreOfficeKitDocumentType>(priv->m_pDocument->pClass->getDocumentType(priv->m_pDocument));
2368 gdk_threads_add_idle(postDocumentLoad, pDocView);
2369 g_task_return_boolean (task, true);
2373 static void
2374 setPartInThread(gpointer data)
2376 GTask* task = G_TASK(data);
2377 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2378 LOKDocViewPrivate& priv = getPrivate(pDocView);
2379 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2380 int nPart = pLOEvent->m_nPart;
2382 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2383 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2384 priv->m_pDocument->pClass->setPart( priv->m_pDocument, nPart );
2385 aGuard.unlock();
2387 lok_doc_view_reset_view(pDocView);
2390 static void
2391 setPartmodeInThread(gpointer data)
2393 GTask* task = G_TASK(data);
2394 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2395 LOKDocViewPrivate& priv = getPrivate(pDocView);
2396 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2397 int nPartMode = pLOEvent->m_nPartMode;
2399 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2400 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2401 priv->m_pDocument->pClass->setPartMode( priv->m_pDocument, nPartMode );
2404 static void
2405 setEditInThread(gpointer data)
2407 GTask* task = G_TASK(data);
2408 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2409 LOKDocViewPrivate& priv = getPrivate(pDocView);
2410 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2411 bool bWasEdit = priv->m_bEdit;
2412 bool bEdit = pLOEvent->m_bEdit;
2414 if (!priv->m_bEdit && bEdit)
2415 g_info("lok_doc_view_set_edit: entering edit mode");
2416 else if (priv->m_bEdit && !bEdit)
2418 g_info("lok_doc_view_set_edit: leaving edit mode");
2419 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2420 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2421 priv->m_pDocument->pClass->resetSelection(priv->m_pDocument);
2423 priv->m_bEdit = bEdit;
2424 g_signal_emit(pDocView, doc_view_signals[EDIT_CHANGED], 0, bWasEdit);
2425 gdk_threads_add_idle(queueDraw, GTK_WIDGET(pDocView));
2428 static void
2429 postCommandInThread (gpointer data)
2431 GTask* task = G_TASK(data);
2432 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2433 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2434 LOKDocViewPrivate& priv = getPrivate(pDocView);
2436 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2437 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2438 std::stringstream ss;
2439 ss << "lok::Document::postUnoCommand(" << pLOEvent->m_pCommand << ", " << pLOEvent->m_pArguments << ")";
2440 g_info("%s", ss.str().c_str());
2441 priv->m_pDocument->pClass->postUnoCommand(priv->m_pDocument, pLOEvent->m_pCommand, pLOEvent->m_pArguments, pLOEvent->m_bNotifyWhenFinished);
2444 static void
2445 paintTileInThread (gpointer data)
2447 GTask* task = G_TASK(data);
2448 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2449 LOKDocViewPrivate& priv = getPrivate(pDocView);
2450 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2451 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
2452 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
2454 // check if "source" tile buffer is different from "current" tile buffer
2455 if (pLOEvent->m_pTileBuffer != &*priv->m_pTileBuffer)
2457 pLOEvent->m_pTileBuffer = nullptr;
2458 g_task_return_new_error(task,
2459 LOK_TILEBUFFER_ERROR,
2460 LOK_TILEBUFFER_CHANGED,
2461 "TileBuffer has changed");
2462 return;
2464 std::unique_ptr<TileBuffer>& buffer = priv->m_pTileBuffer;
2465 if (buffer->hasValidTile(pLOEvent->m_nPaintTileX, pLOEvent->m_nPaintTileY))
2466 return;
2468 cairo_surface_t *pSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nTileSizePixelsScaled, nTileSizePixelsScaled);
2469 if (cairo_surface_status(pSurface) != CAIRO_STATUS_SUCCESS)
2471 cairo_surface_destroy(pSurface);
2472 g_task_return_new_error(task,
2473 LOK_TILEBUFFER_ERROR,
2474 LOK_TILEBUFFER_MEMORY,
2475 "Error allocating Surface");
2476 return;
2479 unsigned char* pBuffer = cairo_image_surface_get_data(pSurface);
2480 GdkRectangle aTileRectangle;
2481 aTileRectangle.x = pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor) * pLOEvent->m_nPaintTileY;
2482 aTileRectangle.y = pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor) * pLOEvent->m_nPaintTileX;
2484 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2485 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2486 std::stringstream ss;
2487 GTimer* aTimer = g_timer_new();
2488 gulong nElapsedMs;
2489 ss << "lok::Document::paintTile(" << static_cast<void*>(pBuffer) << ", "
2490 << nTileSizePixelsScaled << ", " << nTileSizePixelsScaled << ", "
2491 << aTileRectangle.x << ", " << aTileRectangle.y << ", "
2492 << pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor) << ", "
2493 << pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor) << ")";
2495 priv->m_pDocument->pClass->paintTile(priv->m_pDocument,
2496 pBuffer,
2497 nTileSizePixelsScaled, nTileSizePixelsScaled,
2498 aTileRectangle.x, aTileRectangle.y,
2499 pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor),
2500 pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor));
2501 aGuard.unlock();
2503 g_timer_elapsed(aTimer, &nElapsedMs);
2504 ss << " rendered in " << (nElapsedMs / 1000.) << " milliseconds";
2505 g_info("%s", ss.str().c_str());
2506 g_timer_destroy(aTimer);
2508 cairo_surface_mark_dirty(pSurface);
2510 // Its likely that while the tilebuffer has changed, one of the paint tile
2511 // requests has passed the previous check at start of this function, and has
2512 // rendered the tile already. We want to stop such rendered tiles from being
2513 // stored in new tile buffer.
2514 if (pLOEvent->m_pTileBuffer != &*priv->m_pTileBuffer)
2516 pLOEvent->m_pTileBuffer = nullptr;
2517 g_task_return_new_error(task,
2518 LOK_TILEBUFFER_ERROR,
2519 LOK_TILEBUFFER_CHANGED,
2520 "TileBuffer has changed");
2521 return;
2524 g_task_return_pointer(task, pSurface, reinterpret_cast<GDestroyNotify>(cairo_surface_destroy));
2528 static void
2529 lokThreadFunc(gpointer data, gpointer /*user_data*/)
2531 GTask* task = G_TASK(data);
2532 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2533 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2534 LOKDocViewPrivate& priv = getPrivate(pDocView);
2536 switch (pLOEvent->m_nType)
2538 case LOK_LOAD_DOC:
2539 openDocumentInThread(task);
2540 break;
2541 case LOK_POST_COMMAND:
2542 postCommandInThread(task);
2543 break;
2544 case LOK_SET_EDIT:
2545 setEditInThread(task);
2546 break;
2547 case LOK_SET_PART:
2548 setPartInThread(task);
2549 break;
2550 case LOK_SET_PARTMODE:
2551 setPartmodeInThread(task);
2552 break;
2553 case LOK_POST_KEY:
2554 // view-only/editable mode already checked during signal key signal emission
2555 postKeyEventInThread(task);
2556 break;
2557 case LOK_PAINT_TILE:
2558 paintTileInThread(task);
2559 break;
2560 case LOK_POST_MOUSE_EVENT:
2561 postMouseEventInThread(task);
2562 break;
2563 case LOK_SET_GRAPHIC_SELECTION:
2564 if (priv->m_bEdit)
2565 setGraphicSelectionInThread(task);
2566 else
2567 g_info ("LOK_SET_GRAPHIC_SELECTION: skipping graphical operation in view-only mode");
2568 break;
2569 case LOK_SET_CLIENT_ZOOM:
2570 setClientZoomInThread(task);
2571 break;
2574 g_object_unref(task);
2577 static void
2578 onStyleContextChanged (LOKDocView* pDocView)
2580 // The scale factor might have changed
2581 updateClientZoom (pDocView);
2582 gtk_widget_queue_draw (GTK_WIDGET (pDocView));
2585 static void lok_doc_view_init (LOKDocView* pDocView)
2587 LOKDocViewPrivate& priv = getPrivate(pDocView);
2588 priv.m_pImpl = new LOKDocViewPrivateImpl();
2590 gtk_widget_add_events(GTK_WIDGET(pDocView),
2591 GDK_BUTTON_PRESS_MASK
2592 |GDK_BUTTON_RELEASE_MASK
2593 |GDK_BUTTON_MOTION_MASK
2594 |GDK_KEY_PRESS_MASK
2595 |GDK_KEY_RELEASE_MASK);
2597 priv->lokThreadPool = g_thread_pool_new(lokThreadFunc,
2598 nullptr,
2600 FALSE,
2601 nullptr);
2603 g_signal_connect (pDocView, "style-updated", G_CALLBACK(onStyleContextChanged), nullptr);
2606 static void lok_doc_view_set_property (GObject* object, guint propId, const GValue *value, GParamSpec *pspec)
2608 LOKDocView* pDocView = LOK_DOC_VIEW (object);
2609 LOKDocViewPrivate& priv = getPrivate(pDocView);
2610 bool bDocPasswordEnabled = priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD;
2611 bool bDocPasswordToModifyEnabled = priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY;
2612 bool bTiledAnnotationsEnabled = !(priv->m_nLOKFeatures & LOK_FEATURE_NO_TILED_ANNOTATIONS);
2614 switch (propId)
2616 case PROP_LO_PATH:
2617 priv->m_aLOPath = g_value_get_string (value);
2618 break;
2619 case PROP_LO_UNIPOLL:
2620 priv->m_bUnipoll = g_value_get_boolean (value);
2621 break;
2622 case PROP_LO_POINTER:
2623 priv->m_pOffice = static_cast<LibreOfficeKit*>(g_value_get_pointer(value));
2624 break;
2625 case PROP_USER_PROFILE_URL:
2626 if (const gchar* pUserProfile = g_value_get_string(value))
2627 priv->m_aUserProfileURL = pUserProfile;
2628 break;
2629 case PROP_DOC_PATH:
2630 priv->m_aDocPath = g_value_get_string (value);
2631 break;
2632 case PROP_DOC_POINTER:
2633 priv->m_pDocument = static_cast<LibreOfficeKitDocument*>(g_value_get_pointer(value));
2634 priv->m_eDocumentType = static_cast<LibreOfficeKitDocumentType>(priv->m_pDocument->pClass->getDocumentType(priv->m_pDocument));
2635 break;
2636 case PROP_EDITABLE:
2637 lok_doc_view_set_edit (pDocView, g_value_get_boolean (value));
2638 break;
2639 case PROP_ZOOM:
2640 lok_doc_view_set_zoom (pDocView, g_value_get_float (value));
2641 break;
2642 case PROP_DOC_WIDTH:
2643 priv->m_nDocumentWidthTwips = g_value_get_long (value);
2644 break;
2645 case PROP_DOC_HEIGHT:
2646 priv->m_nDocumentHeightTwips = g_value_get_long (value);
2647 break;
2648 case PROP_DOC_PASSWORD:
2649 if (bool(g_value_get_boolean (value)) != bDocPasswordEnabled)
2651 priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_DOCUMENT_PASSWORD;
2652 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2654 break;
2655 case PROP_DOC_PASSWORD_TO_MODIFY:
2656 if ( bool(g_value_get_boolean (value)) != bDocPasswordToModifyEnabled)
2658 priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY;
2659 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2661 break;
2662 case PROP_TILED_ANNOTATIONS:
2663 if ( bool(g_value_get_boolean (value)) != bTiledAnnotationsEnabled)
2665 priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_NO_TILED_ANNOTATIONS;
2666 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2668 break;
2669 default:
2670 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec);
2674 static void lok_doc_view_get_property (GObject* object, guint propId, GValue *value, GParamSpec *pspec)
2676 LOKDocView* pDocView = LOK_DOC_VIEW (object);
2677 LOKDocViewPrivate& priv = getPrivate(pDocView);
2679 switch (propId)
2681 case PROP_LO_PATH:
2682 g_value_set_string (value, priv->m_aLOPath.c_str());
2683 break;
2684 case PROP_LO_UNIPOLL:
2685 g_value_set_boolean (value, priv->m_bUnipoll);
2686 break;
2687 case PROP_LO_POINTER:
2688 g_value_set_pointer(value, priv->m_pOffice);
2689 break;
2690 case PROP_USER_PROFILE_URL:
2691 g_value_set_string(value, priv->m_aUserProfileURL.c_str());
2692 break;
2693 case PROP_DOC_PATH:
2694 g_value_set_string (value, priv->m_aDocPath.c_str());
2695 break;
2696 case PROP_DOC_POINTER:
2697 g_value_set_pointer(value, priv->m_pDocument);
2698 break;
2699 case PROP_EDITABLE:
2700 g_value_set_boolean (value, priv->m_bEdit);
2701 break;
2702 case PROP_LOAD_PROGRESS:
2703 g_value_set_double (value, priv->m_nLoadProgress);
2704 break;
2705 case PROP_ZOOM:
2706 g_value_set_float (value, priv->m_fZoom);
2707 break;
2708 case PROP_IS_LOADING:
2709 g_value_set_boolean (value, priv->m_bIsLoading);
2710 break;
2711 case PROP_IS_INITIALIZED:
2712 g_value_set_boolean (value, priv->m_bInit);
2713 break;
2714 case PROP_DOC_WIDTH:
2715 g_value_set_long (value, priv->m_nDocumentWidthTwips);
2716 break;
2717 case PROP_DOC_HEIGHT:
2718 g_value_set_long (value, priv->m_nDocumentHeightTwips);
2719 break;
2720 case PROP_CAN_ZOOM_IN:
2721 g_value_set_boolean (value, priv->m_bCanZoomIn);
2722 break;
2723 case PROP_CAN_ZOOM_OUT:
2724 g_value_set_boolean (value, priv->m_bCanZoomOut);
2725 break;
2726 case PROP_DOC_PASSWORD:
2727 g_value_set_boolean (value, (priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD) != 0);
2728 break;
2729 case PROP_DOC_PASSWORD_TO_MODIFY:
2730 g_value_set_boolean (value, (priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY) != 0);
2731 break;
2732 case PROP_TILED_ANNOTATIONS:
2733 g_value_set_boolean (value, !(priv->m_nLOKFeatures & LOK_FEATURE_NO_TILED_ANNOTATIONS));
2734 break;
2735 default:
2736 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec);
2740 static gboolean lok_doc_view_draw (GtkWidget* pWidget, cairo_t* pCairo)
2742 LOKDocView *pDocView = LOK_DOC_VIEW (pWidget);
2744 renderDocument (pDocView, pCairo);
2745 renderOverlay (pDocView, pCairo);
2747 return FALSE;
2750 //rhbz#1444437 finalize may not occur immediately when this widget is destroyed
2751 //it may happen during GC of javascript, e.g. in gnome-documents but "destroy"
2752 //will be called promptly, so close documents in destroy, not finalize
2753 static void lok_doc_view_destroy (GtkWidget* widget)
2755 LOKDocView* pDocView = LOK_DOC_VIEW (widget);
2756 LOKDocViewPrivate& priv = getPrivate(pDocView);
2758 // Ignore notifications sent to this view on shutdown.
2759 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2760 if (priv->m_pDocument)
2762 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2763 priv->m_pDocument->pClass->registerCallback(priv->m_pDocument, nullptr, nullptr);
2766 if (priv->lokThreadPool)
2768 g_thread_pool_free(priv->lokThreadPool, true, true);
2769 priv->lokThreadPool = nullptr;
2772 aGuard.unlock();
2774 if (priv->m_pDocument)
2776 // This call may drop several views - e.g., embedded OLE in-place clients
2777 priv->m_pDocument->pClass->destroyView(priv->m_pDocument, priv->m_nViewId);
2778 if (priv->m_pDocument->pClass->getViewsCount(priv->m_pDocument) == 0)
2780 // Last view(s) gone
2781 priv->m_pDocument->pClass->destroy (priv->m_pDocument);
2782 priv->m_pDocument = nullptr;
2783 if (priv->m_pOffice)
2785 priv->m_pOffice->pClass->destroy (priv->m_pOffice);
2786 priv->m_pOffice = nullptr;
2791 GTK_WIDGET_CLASS (lok_doc_view_parent_class)->destroy (widget);
2794 static void lok_doc_view_finalize (GObject* object)
2796 LOKDocView* pDocView = LOK_DOC_VIEW (object);
2797 LOKDocViewPrivate& priv = getPrivate(pDocView);
2799 delete priv.m_pImpl;
2800 priv.m_pImpl = nullptr;
2802 G_OBJECT_CLASS (lok_doc_view_parent_class)->finalize (object);
2805 // kicks the mainloop awake
2806 static gboolean timeout_wakeup(void *)
2808 return FALSE;
2811 // integrate our mainloop with LOK's
2812 static int lok_poll_callback(void*, int timeoutUs)
2814 if (timeoutUs)
2816 guint timeout = g_timeout_add(timeoutUs / 1000, timeout_wakeup, nullptr);
2817 g_main_context_iteration(nullptr, true);
2818 g_source_remove(timeout);
2820 else
2821 g_main_context_iteration(nullptr, FALSE);
2823 return 0;
2826 // thread-safe wakeup of our mainloop
2827 static void lok_wake_callback(void *)
2829 g_main_context_wakeup(nullptr);
2832 static gboolean spin_lok_loop(void *pData)
2834 LOKDocView *pDocView = LOK_DOC_VIEW (pData);
2835 LOKDocViewPrivate& priv = getPrivate(pDocView);
2836 priv->m_pOffice->pClass->runLoop(priv->m_pOffice, lok_poll_callback, lok_wake_callback, nullptr);
2837 return FALSE;
2840 // Update the client's view size
2841 static void updateClientZoom(LOKDocView *pDocView)
2843 LOKDocViewPrivate& priv = getPrivate(pDocView);
2844 if (!priv->m_fZoom)
2845 return; // Not initialized yet?
2846 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
2847 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
2848 GError* error = nullptr;
2850 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2851 LOEvent* pLOEvent = new LOEvent(LOK_SET_CLIENT_ZOOM);
2852 pLOEvent->m_nTilePixelWidth = nTileSizePixelsScaled;
2853 pLOEvent->m_nTilePixelHeight = nTileSizePixelsScaled;
2854 pLOEvent->m_nTileTwipWidth = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom * nScaleFactor);
2855 pLOEvent->m_nTileTwipHeight = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom * nScaleFactor);
2856 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2858 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2859 if (error != nullptr)
2861 g_warning("Unable to call LOK_SET_CLIENT_ZOOM: %s", error->message);
2862 g_clear_error(&error);
2864 g_object_unref(task);
2866 priv->m_nTileSizeTwips = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom * nScaleFactor);
2869 static gboolean lok_doc_view_initable_init (GInitable *initable, GCancellable* /*cancellable*/, GError **error)
2871 LOKDocView *pDocView = LOK_DOC_VIEW (initable);
2872 LOKDocViewPrivate& priv = getPrivate(pDocView);
2874 if (priv->m_pOffice != nullptr)
2875 return true;
2877 if (priv->m_bUnipoll)
2878 (void)g_setenv("SAL_LOK_OPTIONS", "unipoll", FALSE);
2880 static const char testingLangs[] = "de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru";
2881 (void)g_setenv("LOK_ALLOWLIST_LANGUAGES", testingLangs, FALSE);
2883 priv->m_pOffice = lok_init_2(priv->m_aLOPath.c_str(), priv->m_aUserProfileURL.empty() ? nullptr : priv->m_aUserProfileURL.c_str());
2885 if (priv->m_pOffice == nullptr)
2887 g_set_error (error,
2888 g_quark_from_static_string ("LOK initialization error"), 0,
2889 "Failed to get LibreOfficeKit context. Make sure path (%s) is correct",
2890 priv->m_aLOPath.c_str());
2891 return FALSE;
2893 priv->m_nLOKFeatures |= LOK_FEATURE_PART_IN_INVALIDATION_CALLBACK;
2894 priv->m_nLOKFeatures |= LOK_FEATURE_VIEWID_IN_VISCURSOR_INVALIDATION_CALLBACK;
2895 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2897 if (priv->m_bUnipoll)
2898 g_idle_add(spin_lok_loop, pDocView);
2900 return true;
2903 static void lok_doc_view_initable_iface_init (GInitableIface *iface)
2905 iface->init = lok_doc_view_initable_init;
2908 static void lok_doc_view_class_init (LOKDocViewClass* pClass)
2910 GObjectClass *pGObjectClass = G_OBJECT_CLASS(pClass);
2911 GtkWidgetClass *pWidgetClass = GTK_WIDGET_CLASS(pClass);
2913 pGObjectClass->get_property = lok_doc_view_get_property;
2914 pGObjectClass->set_property = lok_doc_view_set_property;
2915 pGObjectClass->finalize = lok_doc_view_finalize;
2917 pWidgetClass->draw = lok_doc_view_draw;
2918 pWidgetClass->button_press_event = lok_doc_view_signal_button;
2919 pWidgetClass->button_release_event = lok_doc_view_signal_button;
2920 pWidgetClass->key_press_event = signalKey;
2921 pWidgetClass->key_release_event = signalKey;
2922 pWidgetClass->motion_notify_event = lok_doc_view_signal_motion;
2923 pWidgetClass->destroy = lok_doc_view_destroy;
2926 * LOKDocView:lopath:
2928 * The absolute path of the LibreOffice install.
2930 properties[PROP_LO_PATH] =
2931 g_param_spec_string("lopath",
2932 "LO Path",
2933 "LibreOffice Install Path",
2934 nullptr,
2935 static_cast<GParamFlags>(G_PARAM_READWRITE |
2936 G_PARAM_CONSTRUCT_ONLY |
2937 G_PARAM_STATIC_STRINGS));
2940 * LOKDocView:unipoll:
2942 * Whether we use our own unified polling mainloop in place of glib's
2944 properties[PROP_LO_UNIPOLL] =
2945 g_param_spec_boolean("unipoll",
2946 "Unified Polling",
2947 "Whether we use a custom unified polling loop",
2948 FALSE,
2949 static_cast<GParamFlags>(G_PARAM_READWRITE |
2950 G_PARAM_CONSTRUCT_ONLY |
2951 G_PARAM_STATIC_STRINGS));
2953 * LOKDocView:lopointer:
2955 * A LibreOfficeKit* in case lok_init() is already called
2956 * previously.
2958 properties[PROP_LO_POINTER] =
2959 g_param_spec_pointer("lopointer",
2960 "LO Pointer",
2961 "A LibreOfficeKit* from lok_init()",
2962 static_cast<GParamFlags>(G_PARAM_READWRITE |
2963 G_PARAM_CONSTRUCT_ONLY |
2964 G_PARAM_STATIC_STRINGS));
2967 * LOKDocView:userprofileurl:
2969 * The absolute path of the LibreOffice user profile.
2971 properties[PROP_USER_PROFILE_URL] =
2972 g_param_spec_string("userprofileurl",
2973 "User profile path",
2974 "LibreOffice user profile path",
2975 nullptr,
2976 static_cast<GParamFlags>(G_PARAM_READWRITE |
2977 G_PARAM_CONSTRUCT_ONLY |
2978 G_PARAM_STATIC_STRINGS));
2981 * LOKDocView:docpath:
2983 * The path of the document that is currently being viewed.
2985 properties[PROP_DOC_PATH] =
2986 g_param_spec_string("docpath",
2987 "Document Path",
2988 "The URI of the document to open",
2989 nullptr,
2990 static_cast<GParamFlags>(G_PARAM_READWRITE |
2991 G_PARAM_STATIC_STRINGS));
2994 * LOKDocView:docpointer:
2996 * A LibreOfficeKitDocument* in case documentLoad() is already called
2997 * previously.
2999 properties[PROP_DOC_POINTER] =
3000 g_param_spec_pointer("docpointer",
3001 "Document Pointer",
3002 "A LibreOfficeKitDocument* from documentLoad()",
3003 static_cast<GParamFlags>(G_PARAM_READWRITE |
3004 G_PARAM_STATIC_STRINGS));
3007 * LOKDocView:editable:
3009 * Whether the document loaded inside of #LOKDocView is editable or not.
3011 properties[PROP_EDITABLE] =
3012 g_param_spec_boolean("editable",
3013 "Editable",
3014 "Whether the content is in edit mode or not",
3015 FALSE,
3016 static_cast<GParamFlags>(G_PARAM_READWRITE |
3017 G_PARAM_STATIC_STRINGS));
3020 * LOKDocView:load-progress:
3022 * The percent completion of the current loading operation of the
3023 * document. This can be used for progress bars. Note that this is not a
3024 * very accurate progress indicator, and its value might reset it couple of
3025 * times to 0 and start again. You should not rely on its numbers.
3027 properties[PROP_LOAD_PROGRESS] =
3028 g_param_spec_double("load-progress",
3029 "Estimated Load Progress",
3030 "Shows the progress of the document load operation",
3031 0.0, 1.0, 0.0,
3032 static_cast<GParamFlags>(G_PARAM_READABLE |
3033 G_PARAM_STATIC_STRINGS));
3036 * LOKDocView:zoom-level:
3038 * The current zoom level of the document loaded inside #LOKDocView. The
3039 * default value is 1.0.
3041 properties[PROP_ZOOM] =
3042 g_param_spec_float("zoom-level",
3043 "Zoom Level",
3044 "The current zoom level of the content",
3045 0, 5.0, 1.0,
3046 static_cast<GParamFlags>(G_PARAM_READWRITE |
3047 G_PARAM_STATIC_STRINGS));
3050 * LOKDocView:is-loading:
3052 * Whether the requested document is being loaded or not. %TRUE if it is
3053 * being loaded, otherwise %FALSE.
3055 properties[PROP_IS_LOADING] =
3056 g_param_spec_boolean("is-loading",
3057 "Is Loading",
3058 "Whether the view is loading a document",
3059 FALSE,
3060 static_cast<GParamFlags>(G_PARAM_READABLE |
3061 G_PARAM_STATIC_STRINGS));
3064 * LOKDocView:is-initialized:
3066 * Whether the requested document has completely loaded or not.
3068 properties[PROP_IS_INITIALIZED] =
3069 g_param_spec_boolean("is-initialized",
3070 "Has initialized",
3071 "Whether the view has completely initialized",
3072 FALSE,
3073 static_cast<GParamFlags>(G_PARAM_READABLE |
3074 G_PARAM_STATIC_STRINGS));
3077 * LOKDocView:doc-width:
3079 * The width of the currently loaded document in #LOKDocView in twips.
3081 properties[PROP_DOC_WIDTH] =
3082 g_param_spec_long("doc-width",
3083 "Document Width",
3084 "Width of the document in twips",
3085 0, G_MAXLONG, 0,
3086 static_cast<GParamFlags>(G_PARAM_READWRITE |
3087 G_PARAM_STATIC_STRINGS));
3090 * LOKDocView:doc-height:
3092 * The height of the currently loaded document in #LOKDocView in twips.
3094 properties[PROP_DOC_HEIGHT] =
3095 g_param_spec_long("doc-height",
3096 "Document Height",
3097 "Height of the document in twips",
3098 0, G_MAXLONG, 0,
3099 static_cast<GParamFlags>(G_PARAM_READWRITE |
3100 G_PARAM_STATIC_STRINGS));
3103 * LOKDocView:can-zoom-in:
3105 * It tells whether the view can further be zoomed in or not.
3107 properties[PROP_CAN_ZOOM_IN] =
3108 g_param_spec_boolean("can-zoom-in",
3109 "Can Zoom In",
3110 "Whether the view can be zoomed in further",
3111 true,
3112 static_cast<GParamFlags>(G_PARAM_READABLE
3113 | G_PARAM_STATIC_STRINGS));
3116 * LOKDocView:can-zoom-out:
3118 * It tells whether the view can further be zoomed out or not.
3120 properties[PROP_CAN_ZOOM_OUT] =
3121 g_param_spec_boolean("can-zoom-out",
3122 "Can Zoom Out",
3123 "Whether the view can be zoomed out further",
3124 true,
3125 static_cast<GParamFlags>(G_PARAM_READABLE
3126 | G_PARAM_STATIC_STRINGS));
3129 * LOKDocView:doc-password:
3131 * Set it to true if client supports providing password for viewing
3132 * password protected documents
3134 properties[PROP_DOC_PASSWORD] =
3135 g_param_spec_boolean("doc-password",
3136 "Document password capability",
3137 "Whether client supports providing document passwords",
3138 FALSE,
3139 static_cast<GParamFlags>(G_PARAM_READWRITE
3140 | G_PARAM_STATIC_STRINGS));
3143 * LOKDocView:doc-password-to-modify:
3145 * Set it to true if client supports providing password for edit-protected documents
3147 properties[PROP_DOC_PASSWORD_TO_MODIFY] =
3148 g_param_spec_boolean("doc-password-to-modify",
3149 "Edit document password capability",
3150 "Whether the client supports providing passwords to edit documents",
3151 FALSE,
3152 static_cast<GParamFlags>(G_PARAM_READWRITE
3153 | G_PARAM_STATIC_STRINGS));
3156 * LOKDocView:tiled-annotations-rendering:
3158 * Set it to false if client does not want LO to render comments in tiles and
3159 * instead interested in using comments API to access comments
3161 properties[PROP_TILED_ANNOTATIONS] =
3162 g_param_spec_boolean("tiled-annotations",
3163 "Render comments in tiles",
3164 "Whether the client wants in tile comment rendering",
3165 true,
3166 static_cast<GParamFlags>(G_PARAM_READWRITE
3167 | G_PARAM_STATIC_STRINGS));
3169 g_object_class_install_properties(pGObjectClass, PROP_LAST, properties);
3172 * LOKDocView::load-changed:
3173 * @pDocView: the #LOKDocView on which the signal is emitted
3174 * @fLoadProgress: the new progress value
3176 doc_view_signals[LOAD_CHANGED] =
3177 g_signal_new("load-changed",
3178 G_TYPE_FROM_CLASS (pGObjectClass),
3179 G_SIGNAL_RUN_FIRST,
3181 nullptr, nullptr,
3182 g_cclosure_marshal_VOID__DOUBLE,
3183 G_TYPE_NONE, 1,
3184 G_TYPE_DOUBLE);
3187 * LOKDocView::edit-changed:
3188 * @pDocView: the #LOKDocView on which the signal is emitted
3189 * @bEdit: the new edit value of the view
3191 doc_view_signals[EDIT_CHANGED] =
3192 g_signal_new("edit-changed",
3193 G_TYPE_FROM_CLASS (pGObjectClass),
3194 G_SIGNAL_RUN_FIRST,
3196 nullptr, nullptr,
3197 g_cclosure_marshal_VOID__BOOLEAN,
3198 G_TYPE_NONE, 1,
3199 G_TYPE_BOOLEAN);
3202 * LOKDocView::command-changed:
3203 * @pDocView: the #LOKDocView on which the signal is emitted
3204 * @aCommand: the command that was changed
3206 doc_view_signals[COMMAND_CHANGED] =
3207 g_signal_new("command-changed",
3208 G_TYPE_FROM_CLASS(pGObjectClass),
3209 G_SIGNAL_RUN_FIRST,
3211 nullptr, nullptr,
3212 g_cclosure_marshal_VOID__STRING,
3213 G_TYPE_NONE, 1,
3214 G_TYPE_STRING);
3217 * LOKDocView::search-not-found:
3218 * @pDocView: the #LOKDocView on which the signal is emitted
3219 * @aCommand: the string for which the search was not found.
3221 doc_view_signals[SEARCH_NOT_FOUND] =
3222 g_signal_new("search-not-found",
3223 G_TYPE_FROM_CLASS(pGObjectClass),
3224 G_SIGNAL_RUN_FIRST,
3226 nullptr, nullptr,
3227 g_cclosure_marshal_VOID__STRING,
3228 G_TYPE_NONE, 1,
3229 G_TYPE_STRING);
3232 * LOKDocView::part-changed:
3233 * @pDocView: the #LOKDocView on which the signal is emitted
3234 * @aCommand: the part number which the view changed to
3236 doc_view_signals[PART_CHANGED] =
3237 g_signal_new("part-changed",
3238 G_TYPE_FROM_CLASS(pGObjectClass),
3239 G_SIGNAL_RUN_FIRST,
3241 nullptr, nullptr,
3242 g_cclosure_marshal_VOID__INT,
3243 G_TYPE_NONE, 1,
3244 G_TYPE_INT);
3247 * LOKDocView::size-changed:
3248 * @pDocView: the #LOKDocView on which the signal is emitted
3249 * @aCommand: NULL, we just notify that want to notify the UI elements that are interested.
3251 doc_view_signals[SIZE_CHANGED] =
3252 g_signal_new("size-changed",
3253 G_TYPE_FROM_CLASS(pGObjectClass),
3254 G_SIGNAL_RUN_FIRST,
3256 nullptr, nullptr,
3257 g_cclosure_marshal_VOID__VOID,
3258 G_TYPE_NONE, 1,
3259 G_TYPE_INT);
3262 * LOKDocView::hyperlinked-clicked:
3263 * @pDocView: the #LOKDocView on which the signal is emitted
3264 * @aHyperlink: the URI which the application should handle
3266 doc_view_signals[HYPERLINK_CLICKED] =
3267 g_signal_new("hyperlink-clicked",
3268 G_TYPE_FROM_CLASS(pGObjectClass),
3269 G_SIGNAL_RUN_FIRST,
3271 nullptr, nullptr,
3272 g_cclosure_marshal_VOID__STRING,
3273 G_TYPE_NONE, 1,
3274 G_TYPE_STRING);
3277 * LOKDocView::cursor-changed:
3278 * @pDocView: the #LOKDocView on which the signal is emitted
3279 * @nX: The new cursor position (X coordinate) in pixels
3280 * @nY: The new cursor position (Y coordinate) in pixels
3281 * @nWidth: The width of new cursor
3282 * @nHeight: The height of new cursor
3284 doc_view_signals[CURSOR_CHANGED] =
3285 g_signal_new("cursor-changed",
3286 G_TYPE_FROM_CLASS(pGObjectClass),
3287 G_SIGNAL_RUN_FIRST,
3289 nullptr, nullptr,
3290 g_cclosure_marshal_generic,
3291 G_TYPE_NONE, 4,
3292 G_TYPE_INT, G_TYPE_INT,
3293 G_TYPE_INT, G_TYPE_INT);
3296 * LOKDocView::search-result-count:
3297 * @pDocView: the #LOKDocView on which the signal is emitted
3298 * @aCommand: number of matches.
3300 doc_view_signals[SEARCH_RESULT_COUNT] =
3301 g_signal_new("search-result-count",
3302 G_TYPE_FROM_CLASS(pGObjectClass),
3303 G_SIGNAL_RUN_FIRST,
3305 nullptr, nullptr,
3306 g_cclosure_marshal_VOID__STRING,
3307 G_TYPE_NONE, 1,
3308 G_TYPE_STRING);
3311 * LOKDocView::command-result:
3312 * @pDocView: the #LOKDocView on which the signal is emitted
3313 * @aCommand: JSON containing the info about the command that finished,
3314 * and its success status.
3316 doc_view_signals[COMMAND_RESULT] =
3317 g_signal_new("command-result",
3318 G_TYPE_FROM_CLASS(pGObjectClass),
3319 G_SIGNAL_RUN_FIRST,
3321 nullptr, nullptr,
3322 g_cclosure_marshal_VOID__STRING,
3323 G_TYPE_NONE, 1,
3324 G_TYPE_STRING);
3327 * LOKDocView::address-changed:
3328 * @pDocView: the #LOKDocView on which the signal is emitted
3329 * @aCommand: formula text content
3331 doc_view_signals[ADDRESS_CHANGED] =
3332 g_signal_new("address-changed",
3333 G_TYPE_FROM_CLASS(pGObjectClass),
3334 G_SIGNAL_RUN_FIRST,
3336 nullptr, nullptr,
3337 g_cclosure_marshal_VOID__STRING,
3338 G_TYPE_NONE, 1,
3339 G_TYPE_STRING);
3342 * LOKDocView::formula-changed:
3343 * @pDocView: the #LOKDocView on which the signal is emitted
3344 * @aCommand: formula text content
3346 doc_view_signals[FORMULA_CHANGED] =
3347 g_signal_new("formula-changed",
3348 G_TYPE_FROM_CLASS(pGObjectClass),
3349 G_SIGNAL_RUN_FIRST,
3351 nullptr, nullptr,
3352 g_cclosure_marshal_VOID__STRING,
3353 G_TYPE_NONE, 1,
3354 G_TYPE_STRING);
3357 * LOKDocView::text-selection:
3358 * @pDocView: the #LOKDocView on which the signal is emitted
3359 * @bIsTextSelected: whether text selected is non-null
3361 doc_view_signals[TEXT_SELECTION] =
3362 g_signal_new("text-selection",
3363 G_TYPE_FROM_CLASS(pGObjectClass),
3364 G_SIGNAL_RUN_FIRST,
3366 nullptr, nullptr,
3367 g_cclosure_marshal_VOID__BOOLEAN,
3368 G_TYPE_NONE, 1,
3369 G_TYPE_BOOLEAN);
3372 * LOKDocView::content-control:
3373 * @pDocView: the #LOKDocView on which the signal is emitted
3374 * @pPayload: the JSON string containing the information about ruler properties
3376 doc_view_signals[CONTENT_CONTROL] =
3377 g_signal_new("content-control",
3378 G_TYPE_FROM_CLASS(pGObjectClass),
3379 G_SIGNAL_RUN_FIRST,
3381 nullptr, nullptr,
3382 g_cclosure_marshal_generic,
3383 G_TYPE_NONE, 1,
3384 G_TYPE_STRING);
3387 * LOKDocView::password-required:
3388 * @pDocView: the #LOKDocView on which the signal is emitted
3389 * @pUrl: URL of the document for which password is required
3390 * @bModify: whether password id required to modify the document
3391 * This is true when password is required to edit the document,
3392 * while it can still be viewed without password. In such cases, provide a NULL
3393 * password for read-only access to the document.
3394 * If false, password is required for opening the document, and document
3395 * cannot be opened without providing a valid password.
3397 * Password must be provided by calling lok_doc_view_set_document_password
3398 * function with pUrl as provided by the callback.
3400 * Upon entering an invalid password, another `password-required` signal is
3401 * emitted.
3402 * Upon entering a valid password, document starts to load.
3403 * Upon entering a NULL password: if bModify is %TRUE, document starts to
3404 * open in view-only mode, else loading of document is aborted.
3406 doc_view_signals[PASSWORD_REQUIRED] =
3407 g_signal_new("password-required",
3408 G_TYPE_FROM_CLASS(pGObjectClass),
3409 G_SIGNAL_RUN_FIRST,
3411 nullptr, nullptr,
3412 g_cclosure_marshal_generic,
3413 G_TYPE_NONE, 2,
3414 G_TYPE_STRING,
3415 G_TYPE_BOOLEAN);
3418 * LOKDocView::comment:
3419 * @pDocView: the #LOKDocView on which the signal is emitted
3420 * @pComment: the JSON string containing comment notification
3421 * The has following structure containing the information telling whether
3422 * the comment has been added, deleted or modified.
3423 * The example:
3425 * "comment": {
3426 * "action": "Add",
3427 * "id": "11",
3428 * "parent": "4",
3429 * "author": "Unknown Author",
3430 * "text": "This is a comment",
3431 * "dateTime": "2016-08-18T13:13:00",
3432 * "anchorPos": "4529, 3906",
3433 * "textRange": "1418, 3906, 3111, 919"
3436 * 'action' can be 'Add', 'Remove' or 'Modify' depending on whether
3437 * comment has been added, removed or modified.
3438 * 'parent' is a non-zero comment id if this comment is a reply comment,
3439 * otherwise it's a root comment.
3441 doc_view_signals[COMMENT] =
3442 g_signal_new("comment",
3443 G_TYPE_FROM_CLASS(pGObjectClass),
3444 G_SIGNAL_RUN_FIRST,
3446 nullptr, nullptr,
3447 g_cclosure_marshal_generic,
3448 G_TYPE_NONE, 1,
3449 G_TYPE_STRING);
3452 * LOKDocView::ruler:
3453 * @pDocView: the #LOKDocView on which the signal is emitted
3454 * @pPayload: the JSON string containing the information about ruler properties
3456 * The payload format is:
3459 * "margin1": "...",
3460 * "margin2": "...",
3461 * "leftOffset": "...",
3462 * "pageOffset": "...",
3463 * "pageWidth": "...",
3464 * "unit": "..."
3467 doc_view_signals[RULER] =
3468 g_signal_new("ruler",
3469 G_TYPE_FROM_CLASS(pGObjectClass),
3470 G_SIGNAL_RUN_FIRST,
3472 nullptr, nullptr,
3473 g_cclosure_marshal_generic,
3474 G_TYPE_NONE, 1,
3475 G_TYPE_STRING);
3478 * LOKDocView::window::
3479 * @pDocView: the #LOKDocView on which the signal is emitted
3480 * @pPayload: the JSON string containing the information about the window
3482 * This signal emits information about external windows like dialogs, autopopups for now.
3484 * The payload format of pPayload is:
3487 * "id": "unique integer id of the dialog",
3488 * "action": "<see below>",
3489 * "type": "<see below>"
3490 * "rectangle": "x, y, width, height"
3493 * "type" tells the type of the window the action is associated with
3494 * - "dialog" - window is a dialog
3495 * - "child" - window is a floating window (combo boxes, etc.)
3497 * "action" can take following values:
3498 * - "created" - window is created in the backend, client can render it now
3499 * - "title_changed" - window's title is changed
3500 * - "size_changed" - window's size is changed
3501 * - "invalidate" - the area as described by "rectangle" is invalidated
3502 * Clients must request the new area
3503 * - "cursor_invalidate" - cursor is invalidated. New position is in "rectangle"
3504 * - "cursor_visible" - cursor visible status is changed. Status is available
3505 * in "visible" field
3506 * - "close" - window is closed
3508 doc_view_signals[WINDOW] =
3509 g_signal_new("window",
3510 G_TYPE_FROM_CLASS(pGObjectClass),
3511 G_SIGNAL_RUN_FIRST,
3513 nullptr, nullptr,
3514 g_cclosure_marshal_generic,
3515 G_TYPE_NONE, 1,
3516 G_TYPE_STRING);
3519 * LOKDocView::invalidate-header::
3520 * @pDocView: the #LOKDocView on which the signal is emitted
3521 * @pPayload: can be either "row", "column", or "all".
3523 * The column/row header is no more valid because of a column/row insertion
3524 * or a similar event. Clients must query a new column/row header set.
3526 * The payload says if we are invalidating a row or column header
3528 doc_view_signals[INVALIDATE_HEADER] =
3529 g_signal_new("invalidate-header",
3530 G_TYPE_FROM_CLASS(pGObjectClass),
3531 G_SIGNAL_RUN_FIRST,
3533 nullptr, nullptr,
3534 g_cclosure_marshal_generic,
3535 G_TYPE_NONE, 1,
3536 G_TYPE_STRING);
3539 SAL_DLLPUBLIC_EXPORT GtkWidget*
3540 lok_doc_view_new (const gchar* pPath, GCancellable *cancellable, GError **error)
3542 return GTK_WIDGET (g_initable_new (LOK_TYPE_DOC_VIEW, cancellable, error,
3543 "lopath", pPath == nullptr ? LOK_PATH : pPath,
3544 "halign", GTK_ALIGN_CENTER,
3545 "valign", GTK_ALIGN_CENTER,
3546 nullptr));
3549 SAL_DLLPUBLIC_EXPORT GtkWidget*
3550 lok_doc_view_new_from_user_profile (const gchar* pPath, const gchar* pUserProfile, GCancellable *cancellable, GError **error)
3552 return GTK_WIDGET(g_initable_new(LOK_TYPE_DOC_VIEW, cancellable, error,
3553 "lopath", pPath == nullptr ? LOK_PATH : pPath,
3554 "userprofileurl", pUserProfile,
3555 "halign", GTK_ALIGN_CENTER,
3556 "valign", GTK_ALIGN_CENTER,
3557 nullptr));
3560 SAL_DLLPUBLIC_EXPORT GtkWidget* lok_doc_view_new_from_widget(LOKDocView* pOldLOKDocView,
3561 const gchar* pRenderingArguments)
3563 LOKDocViewPrivate& pOldPriv = getPrivate(pOldLOKDocView);
3564 GtkWidget* pNewDocView = GTK_WIDGET(g_initable_new(LOK_TYPE_DOC_VIEW, /*cancellable=*/nullptr, /*error=*/nullptr,
3565 "lopath", pOldPriv->m_aLOPath.c_str(),
3566 "userprofileurl", pOldPriv->m_aUserProfileURL.c_str(),
3567 "lopointer", pOldPriv->m_pOffice,
3568 "docpointer", pOldPriv->m_pDocument,
3569 "halign", GTK_ALIGN_CENTER,
3570 "valign", GTK_ALIGN_CENTER,
3571 nullptr));
3573 // No documentLoad(), just a createView().
3574 LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(pNewDocView));
3575 LOKDocViewPrivate& pNewPriv = getPrivate(LOK_DOC_VIEW(pNewDocView));
3576 // Store the view id only later in postDocumentLoad(), as
3577 // initializeForRendering() changes the id in Impress.
3578 pDocument->pClass->createView(pDocument);
3579 pNewPriv->m_aRenderingArguments = pRenderingArguments;
3581 postDocumentLoad(pNewDocView);
3582 return pNewDocView;
3585 SAL_DLLPUBLIC_EXPORT gboolean
3586 lok_doc_view_open_document_finish (LOKDocView* pDocView, GAsyncResult* res, GError** error)
3588 GTask* task = G_TASK(res);
3590 g_return_val_if_fail(g_task_is_valid(res, pDocView), false);
3591 g_return_val_if_fail(g_task_get_source_tag(task) == lok_doc_view_open_document, false);
3592 g_return_val_if_fail(error == nullptr || *error == nullptr, false);
3594 return g_task_propagate_boolean(task, error);
3597 SAL_DLLPUBLIC_EXPORT void
3598 lok_doc_view_open_document (LOKDocView* pDocView,
3599 const gchar* pPath,
3600 const gchar* pRenderingArguments,
3601 GCancellable* cancellable,
3602 GAsyncReadyCallback callback,
3603 gpointer userdata)
3605 GTask* task = g_task_new(pDocView, cancellable, callback, userdata);
3606 LOKDocViewPrivate& priv = getPrivate(pDocView);
3607 GError* error = nullptr;
3609 LOEvent* pLOEvent = new LOEvent(LOK_LOAD_DOC);
3611 g_object_set(G_OBJECT(pDocView), "docpath", pPath, nullptr);
3612 if (pRenderingArguments)
3613 priv->m_aRenderingArguments = pRenderingArguments;
3614 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3615 g_task_set_source_tag(task, reinterpret_cast<gpointer>(lok_doc_view_open_document));
3617 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3618 if (error != nullptr)
3620 g_warning("Unable to call LOK_LOAD_DOC: %s", error->message);
3621 g_clear_error(&error);
3623 g_object_unref(task);
3626 SAL_DLLPUBLIC_EXPORT LibreOfficeKitDocument*
3627 lok_doc_view_get_document (LOKDocView* pDocView)
3629 LOKDocViewPrivate& priv = getPrivate(pDocView);
3630 return priv->m_pDocument;
3633 SAL_DLLPUBLIC_EXPORT void
3634 lok_doc_view_set_visible_area (LOKDocView* pDocView, GdkRectangle* pVisibleArea)
3636 if (!pVisibleArea)
3637 return;
3639 LOKDocViewPrivate& priv = getPrivate(pDocView);
3640 priv->m_aVisibleArea = *pVisibleArea;
3641 priv->m_bVisibleAreaSet = true;
3644 namespace {
3645 // This used to be rtl::math::approxEqual() but since that isn't inline anymore
3646 // in rtl/math.hxx and was moved into libuno_sal as rtl_math_approxEqual() to
3647 // cater for representable integer cases and we don't want to link against
3648 // libuno_sal, we'll have to have an own implementation. The special large
3649 // integer cases seems not be needed here.
3650 bool lok_approxEqual(double a, double b)
3652 static const double e48 = 1.0 / (16777216.0 * 16777216.0);
3653 if (a == b)
3654 return true;
3655 if (a == 0.0 || b == 0.0)
3656 return false;
3657 const double d = fabs(a - b);
3658 return (d < fabs(a) * e48 && d < fabs(b) * e48);
3662 SAL_DLLPUBLIC_EXPORT void
3663 lok_doc_view_set_zoom (LOKDocView* pDocView, float fZoom)
3665 LOKDocViewPrivate& priv = getPrivate(pDocView);
3667 if (!priv->m_pDocument)
3668 return;
3670 // Clamp the input value in [MIN_ZOOM, MAX_ZOOM]
3671 fZoom = fZoom < MIN_ZOOM ? MIN_ZOOM : fZoom;
3672 fZoom = std::min(fZoom, MAX_ZOOM);
3674 if (lok_approxEqual(fZoom, priv->m_fZoom))
3675 return;
3677 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
3678 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
3679 priv->m_fZoom = fZoom;
3680 long nDocumentWidthPixels = twipToPixel(priv->m_nDocumentWidthTwips, fZoom * nScaleFactor);
3681 long nDocumentHeightPixels = twipToPixel(priv->m_nDocumentHeightTwips, fZoom * nScaleFactor);
3682 // Total number of columns in this document.
3683 guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixelsScaled);
3684 priv->m_pTileBuffer = std::make_unique<TileBuffer>(nColumns, nScaleFactor);
3685 gtk_widget_set_size_request(GTK_WIDGET(pDocView),
3686 nDocumentWidthPixels / nScaleFactor,
3687 nDocumentHeightPixels / nScaleFactor);
3689 g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_ZOOM]);
3691 // set properties to indicate if view can be further zoomed in/out
3692 bool bCanZoomIn = priv->m_fZoom < MAX_ZOOM;
3693 bool bCanZoomOut = priv->m_fZoom > MIN_ZOOM;
3694 if (bCanZoomIn != bool(priv->m_bCanZoomIn))
3696 priv->m_bCanZoomIn = bCanZoomIn;
3697 g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_CAN_ZOOM_IN]);
3699 if (bCanZoomOut != bool(priv->m_bCanZoomOut))
3701 priv->m_bCanZoomOut = bCanZoomOut;
3702 g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_CAN_ZOOM_OUT]);
3705 updateClientZoom(pDocView);
3708 SAL_DLLPUBLIC_EXPORT gfloat
3709 lok_doc_view_get_zoom (LOKDocView* pDocView)
3711 LOKDocViewPrivate& priv = getPrivate(pDocView);
3712 return priv->m_fZoom;
3715 SAL_DLLPUBLIC_EXPORT gint
3716 lok_doc_view_get_parts (LOKDocView* pDocView)
3718 LOKDocViewPrivate& priv = getPrivate(pDocView);
3719 if (!priv->m_pDocument)
3720 return -1;
3722 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
3723 setDocumentView(priv->m_pDocument, priv->m_nViewId);
3724 return priv->m_pDocument->pClass->getParts( priv->m_pDocument );
3727 SAL_DLLPUBLIC_EXPORT gint
3728 lok_doc_view_get_part (LOKDocView* pDocView)
3730 LOKDocViewPrivate& priv = getPrivate(pDocView);
3731 if (!priv->m_pDocument)
3732 return -1;
3734 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
3735 setDocumentView(priv->m_pDocument, priv->m_nViewId);
3736 return priv->m_pDocument->pClass->getPart( priv->m_pDocument );
3739 SAL_DLLPUBLIC_EXPORT void
3740 lok_doc_view_set_part (LOKDocView* pDocView, int nPart)
3742 LOKDocViewPrivate& priv = getPrivate(pDocView);
3743 if (!priv->m_pDocument)
3744 return;
3746 if (nPart < 0 || nPart >= priv->m_nParts)
3748 g_warning("Invalid part request : %d", nPart);
3749 return;
3752 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3753 LOEvent* pLOEvent = new LOEvent(LOK_SET_PART);
3754 GError* error = nullptr;
3756 pLOEvent->m_nPart = nPart;
3757 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3759 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3760 if (error != nullptr)
3762 g_warning("Unable to call LOK_SET_PART: %s", error->message);
3763 g_clear_error(&error);
3765 g_object_unref(task);
3766 priv->m_nPartId = nPart;
3769 SAL_DLLPUBLIC_EXPORT void lok_doc_view_send_content_control_event(LOKDocView* pDocView,
3770 const gchar* pArguments)
3772 LOKDocViewPrivate& priv = getPrivate(pDocView);
3773 if (!priv->m_pDocument)
3775 return;
3778 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
3779 setDocumentView(priv->m_pDocument, priv->m_nViewId);
3780 return priv->m_pDocument->pClass->sendContentControlEvent(priv->m_pDocument, pArguments);
3783 SAL_DLLPUBLIC_EXPORT gchar*
3784 lok_doc_view_get_part_name (LOKDocView* pDocView, int nPart)
3786 LOKDocViewPrivate& priv = getPrivate(pDocView);
3787 if (!priv->m_pDocument)
3788 return nullptr;
3790 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
3791 setDocumentView(priv->m_pDocument, priv->m_nViewId);
3792 return priv->m_pDocument->pClass->getPartName( priv->m_pDocument, nPart );
3795 SAL_DLLPUBLIC_EXPORT void
3796 lok_doc_view_set_partmode(LOKDocView* pDocView,
3797 int nPartMode)
3799 LOKDocViewPrivate& priv = getPrivate(pDocView);
3800 if (!priv->m_pDocument)
3801 return;
3803 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3804 LOEvent* pLOEvent = new LOEvent(LOK_SET_PARTMODE);
3805 GError* error = nullptr;
3807 pLOEvent->m_nPartMode = nPartMode;
3808 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3810 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3811 if (error != nullptr)
3813 g_warning("Unable to call LOK_SET_PARTMODE: %s", error->message);
3814 g_clear_error(&error);
3816 g_object_unref(task);
3819 SAL_DLLPUBLIC_EXPORT void
3820 lok_doc_view_reset_view(LOKDocView* pDocView)
3822 LOKDocViewPrivate& priv = getPrivate(pDocView);
3824 if (priv->m_pTileBuffer != nullptr)
3825 priv->m_pTileBuffer->resetAllTiles();
3826 priv->m_nLoadProgress = 0.0;
3828 memset(&priv->m_aVisibleCursor, 0, sizeof(priv->m_aVisibleCursor));
3829 priv->m_bCursorOverlayVisible = false;
3830 priv->m_bCursorVisible = false;
3832 priv->m_nLastButtonPressTime = 0;
3833 priv->m_nLastButtonReleaseTime = 0;
3834 priv->m_aTextSelectionRectangles.clear();
3835 priv->m_aContentControlRectangles.clear();
3837 memset(&priv->m_aTextSelectionStart, 0, sizeof(priv->m_aTextSelectionStart));
3838 memset(&priv->m_aTextSelectionEnd, 0, sizeof(priv->m_aTextSelectionEnd));
3839 memset(&priv->m_aGraphicSelection, 0, sizeof(priv->m_aGraphicSelection));
3840 priv->m_bInDragGraphicSelection = false;
3841 memset(&priv->m_aCellCursor, 0, sizeof(priv->m_aCellCursor));
3843 cairo_surface_destroy(priv->m_pHandleStart);
3844 priv->m_pHandleStart = nullptr;
3845 memset(&priv->m_aHandleStartRect, 0, sizeof(priv->m_aHandleStartRect));
3846 priv->m_bInDragStartHandle = false;
3848 cairo_surface_destroy(priv->m_pHandleMiddle);
3849 priv->m_pHandleMiddle = nullptr;
3850 memset(&priv->m_aHandleMiddleRect, 0, sizeof(priv->m_aHandleMiddleRect));
3851 priv->m_bInDragMiddleHandle = false;
3853 cairo_surface_destroy(priv->m_pHandleEnd);
3854 priv->m_pHandleEnd = nullptr;
3855 memset(&priv->m_aHandleEndRect, 0, sizeof(priv->m_aHandleEndRect));
3856 priv->m_bInDragEndHandle = false;
3858 memset(&priv->m_aGraphicHandleRects, 0, sizeof(priv->m_aGraphicHandleRects));
3859 memset(&priv->m_bInDragGraphicHandles, 0, sizeof(priv->m_bInDragGraphicHandles));
3861 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
3864 SAL_DLLPUBLIC_EXPORT void
3865 lok_doc_view_set_edit(LOKDocView* pDocView,
3866 gboolean bEdit)
3868 LOKDocViewPrivate& priv = getPrivate(pDocView);
3869 if (!priv->m_pDocument)
3870 return;
3872 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3873 LOEvent* pLOEvent = new LOEvent(LOK_SET_EDIT);
3874 GError* error = nullptr;
3876 pLOEvent->m_bEdit = bEdit;
3877 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3879 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3880 if (error != nullptr)
3882 g_warning("Unable to call LOK_SET_EDIT: %s", error->message);
3883 g_clear_error(&error);
3885 g_object_unref(task);
3888 SAL_DLLPUBLIC_EXPORT gboolean
3889 lok_doc_view_get_edit (LOKDocView* pDocView)
3891 LOKDocViewPrivate& priv = getPrivate(pDocView);
3892 return priv->m_bEdit;
3895 SAL_DLLPUBLIC_EXPORT void
3896 lok_doc_view_post_command (LOKDocView* pDocView,
3897 const gchar* pCommand,
3898 const gchar* pArguments,
3899 gboolean bNotifyWhenFinished)
3901 LOKDocViewPrivate& priv = getPrivate(pDocView);
3902 if (!priv->m_pDocument)
3903 return;
3905 if (priv->m_bEdit)
3906 LOKPostCommand(pDocView, pCommand, pArguments, bNotifyWhenFinished);
3907 else
3908 g_info ("LOK_POST_COMMAND: ignoring commands in view-only mode");
3911 SAL_DLLPUBLIC_EXPORT gchar *
3912 lok_doc_view_get_command_values (LOKDocView* pDocView,
3913 const gchar* pCommand)
3915 g_return_val_if_fail (LOK_IS_DOC_VIEW (pDocView), nullptr);
3916 g_return_val_if_fail (pCommand != nullptr, nullptr);
3918 LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(pDocView);
3919 if (!pDocument)
3920 return nullptr;
3922 return pDocument->pClass->getCommandValues(pDocument, pCommand);
3925 SAL_DLLPUBLIC_EXPORT void
3926 lok_doc_view_find_prev (LOKDocView* pDocView,
3927 const gchar* pText,
3928 gboolean bHighlightAll)
3930 doSearch(pDocView, pText, true, bHighlightAll);
3933 SAL_DLLPUBLIC_EXPORT void
3934 lok_doc_view_find_next (LOKDocView* pDocView,
3935 const gchar* pText,
3936 gboolean bHighlightAll)
3938 doSearch(pDocView, pText, false, bHighlightAll);
3941 SAL_DLLPUBLIC_EXPORT void
3942 lok_doc_view_highlight_all (LOKDocView* pDocView,
3943 const gchar* pText)
3945 doSearch(pDocView, pText, false, true);
3948 SAL_DLLPUBLIC_EXPORT gchar*
3949 lok_doc_view_copy_selection (LOKDocView* pDocView,
3950 const gchar* pMimeType,
3951 gchar** pUsedMimeType)
3953 LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(pDocView);
3954 if (!pDocument)
3955 return nullptr;
3957 std::stringstream ss;
3958 ss << "lok::Document::getTextSelection('" << pMimeType << "')";
3959 g_info("%s", ss.str().c_str());
3960 return pDocument->pClass->getTextSelection(pDocument, pMimeType, pUsedMimeType);
3963 SAL_DLLPUBLIC_EXPORT gboolean
3964 lok_doc_view_paste (LOKDocView* pDocView,
3965 const gchar* pMimeType,
3966 const gchar* pData,
3967 gsize nSize)
3969 LOKDocViewPrivate& priv = getPrivate(pDocView);
3970 LibreOfficeKitDocument* pDocument = priv->m_pDocument;
3971 bool ret = false;
3973 if (!pDocument)
3974 return false;
3976 if (!priv->m_bEdit)
3978 g_info ("ignoring paste in view-only mode");
3979 return ret;
3982 if (pData)
3984 std::stringstream ss;
3985 ss << "lok::Document::paste('" << pMimeType << "', '" << std::string(pData, nSize) << ", "<<nSize<<"')";
3986 g_info("%s", ss.str().c_str());
3987 ret = pDocument->pClass->paste(pDocument, pMimeType, pData, nSize);
3990 return ret;
3993 SAL_DLLPUBLIC_EXPORT void
3994 lok_doc_view_set_document_password (LOKDocView* pDocView,
3995 const gchar* pURL,
3996 const gchar* pPassword)
3998 LOKDocViewPrivate& priv = getPrivate(pDocView);
4000 priv->m_pOffice->pClass->setDocumentPassword(priv->m_pOffice, pURL, pPassword);
4003 SAL_DLLPUBLIC_EXPORT gchar*
4004 lok_doc_view_get_version_info (LOKDocView* pDocView)
4006 LOKDocViewPrivate& priv = getPrivate(pDocView);
4008 return priv->m_pOffice->pClass->getVersionInfo(priv->m_pOffice);
4012 SAL_DLLPUBLIC_EXPORT gfloat
4013 lok_doc_view_pixel_to_twip (LOKDocView* pDocView, float fInput)
4015 LOKDocViewPrivate& priv = getPrivate(pDocView);
4016 return pixelToTwip(fInput, priv->m_fZoom);
4019 SAL_DLLPUBLIC_EXPORT gfloat
4020 lok_doc_view_twip_to_pixel (LOKDocView* pDocView, float fInput)
4022 LOKDocViewPrivate& priv = getPrivate(pDocView);
4023 return twipToPixel(fInput, priv->m_fZoom);
4026 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */