nss: upgrade to release 3.73
[LibreOffice.git] / libreofficekit / source / gtk / lokdocview.cxx
blob156224b57351ee9baefa4f74a53f75f63c152801
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, const std::vector<GdkRectangle>& rRectangles = std::vector<GdkRectangle>())
71 : m_nPart(nPart),
72 m_aRectangles(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 view selections. The current view can only see
128 /// them, can't modify them. Key is the view id.
129 std::map<int, ViewRectangles> m_aTextViewSelectionRectangles;
130 /// Position and size of the selection start (as if there would be a cursor caret there).
131 GdkRectangle m_aTextSelectionStart;
132 /// Position and size of the selection end.
133 GdkRectangle m_aTextSelectionEnd;
134 GdkRectangle m_aGraphicSelection;
135 /// Position and size of the graphic view selections. The current view can only
136 /// see them, can't modify them. Key is the view id.
137 std::map<int, ViewRectangle> m_aGraphicViewSelections;
138 GdkRectangle m_aCellCursor;
139 /// Position and size of the cell view cursors. The current view can only
140 /// see them, can't modify them. Key is the view id.
141 std::map<int, ViewRectangle> m_aCellViewCursors;
142 bool m_bInDragGraphicSelection;
143 /// Position, size and color of the reference marks. The current view can only
144 /// see them, can't modify them. Key is the view id.
145 std::vector<std::pair<ViewRectangle, sal_uInt32>> m_aReferenceMarks;
147 /// @name Start/middle/end handle.
148 ///@{
149 /// Bitmap of the text selection start handle.
150 cairo_surface_t* m_pHandleStart;
151 /// Rectangle of the text selection start handle, to know if the user clicked on it or not
152 GdkRectangle m_aHandleStartRect;
153 /// If we are in the middle of a drag of the text selection end handle.
154 bool m_bInDragStartHandle;
155 /// Bitmap of the text selection middle handle.
156 cairo_surface_t* m_pHandleMiddle;
157 /// Rectangle of the text selection middle handle, to know if the user clicked on it or not
158 GdkRectangle m_aHandleMiddleRect;
159 /// If we are in the middle of a drag of the text selection middle handle.
160 bool m_bInDragMiddleHandle;
161 /// Bitmap of the text selection end handle.
162 cairo_surface_t* m_pHandleEnd;
163 /// Rectangle of the text selection end handle, to know if the user clicked on it or not
164 GdkRectangle m_aHandleEndRect;
165 /// If we are in the middle of a drag of the text selection end handle.
166 bool m_bInDragEndHandle;
167 ///@}
169 /// @name Graphic handles.
170 ///@{
171 /// Rectangle of a graphic selection handle, to know if the user clicked on it or not.
172 GdkRectangle m_aGraphicHandleRects[8];
173 /// If we are in the middle of a drag of a graphic selection handle.
174 bool m_bInDragGraphicHandles[8];
175 ///@}
177 /// View ID, returned by createView() or 0 by default.
178 int m_nViewId;
180 /// Cached part ID, returned by getPart().
181 int m_nPartId;
183 /// Cached document type, returned by getDocumentType().
184 LibreOfficeKitDocumentType m_eDocumentType;
186 /// Contains a freshly set zoom level: logic size of a tile.
187 /// It gets reset back to 0 when LOK was informed about this zoom change.
188 int m_nTileSizeTwips;
190 GdkRectangle m_aVisibleArea;
191 bool m_bVisibleAreaSet;
193 /// Event source ID for handleTimeout() of this widget.
194 guint m_nTimeoutId;
196 /// Rectangles of view locks. The current view can only see
197 /// them, can't modify them. Key is the view id.
198 std::map<int, ViewRectangle> m_aViewLockRectangles;
200 LOKDocViewPrivateImpl()
201 : m_nLoadProgress(0),
202 m_bIsLoading(false),
203 m_bInit(false),
204 m_bCanZoomIn(true),
205 m_bCanZoomOut(true),
206 m_bUnipoll(false),
207 m_pOffice(nullptr),
208 m_pDocument(nullptr),
209 lokThreadPool(nullptr),
210 m_fZoom(0),
211 m_nDocumentWidthTwips(0),
212 m_nDocumentHeightTwips(0),
213 m_bEdit(false),
214 m_nLOKFeatures(0),
215 m_nParts(0),
216 m_aVisibleCursor({0, 0, 0, 0}),
217 m_bCursorOverlayVisible(false),
218 m_bCursorVisible(true),
219 m_nLastButtonPressTime(0),
220 m_nLastButtonReleaseTime(0),
221 m_nLastButtonPressed(0),
222 m_nKeyModifier(0),
223 m_aTextSelectionStart({0, 0, 0, 0}),
224 m_aTextSelectionEnd({0, 0, 0, 0}),
225 m_aGraphicSelection({0, 0, 0, 0}),
226 m_aCellCursor({0, 0, 0, 0}),
227 m_bInDragGraphicSelection(false),
228 m_pHandleStart(nullptr),
229 m_aHandleStartRect({0, 0, 0, 0}),
230 m_bInDragStartHandle(false),
231 m_pHandleMiddle(nullptr),
232 m_aHandleMiddleRect({0, 0, 0, 0}),
233 m_bInDragMiddleHandle(false),
234 m_pHandleEnd(nullptr),
235 m_aHandleEndRect({0, 0, 0, 0}),
236 m_bInDragEndHandle(false),
237 m_nViewId(0),
238 m_nPartId(0),
239 m_eDocumentType(LOK_DOCTYPE_OTHER),
240 m_nTileSizeTwips(0),
241 m_aVisibleArea({0, 0, 0, 0}),
242 m_bVisibleAreaSet(false),
243 m_nTimeoutId(0)
245 memset(&m_aGraphicHandleRects, 0, sizeof(m_aGraphicHandleRects));
246 memset(&m_bInDragGraphicHandles, 0, sizeof(m_bInDragGraphicHandles));
249 ~LOKDocViewPrivateImpl()
251 if (m_nTimeoutId)
252 g_source_remove(m_nTimeoutId);
258 /// Wrapper around LOKDocViewPrivateImpl, managed by malloc/memset/free.
259 struct _LOKDocViewPrivate
261 LOKDocViewPrivateImpl* m_pImpl;
263 LOKDocViewPrivateImpl* operator->()
265 return m_pImpl;
269 enum
271 LOAD_CHANGED,
272 EDIT_CHANGED,
273 COMMAND_CHANGED,
274 SEARCH_NOT_FOUND,
275 PART_CHANGED,
276 SIZE_CHANGED,
277 HYPERLINK_CLICKED,
278 CURSOR_CHANGED,
279 SEARCH_RESULT_COUNT,
280 COMMAND_RESULT,
281 ADDRESS_CHANGED,
282 FORMULA_CHANGED,
283 TEXT_SELECTION,
284 PASSWORD_REQUIRED,
285 COMMENT,
286 RULER,
287 WINDOW,
288 INVALIDATE_HEADER,
290 LAST_SIGNAL
293 enum
295 PROP_0,
297 PROP_LO_PATH,
298 PROP_LO_UNIPOLL,
299 PROP_LO_POINTER,
300 PROP_USER_PROFILE_URL,
301 PROP_DOC_PATH,
302 PROP_DOC_POINTER,
303 PROP_EDITABLE,
304 PROP_LOAD_PROGRESS,
305 PROP_ZOOM,
306 PROP_IS_LOADING,
307 PROP_IS_INITIALIZED,
308 PROP_DOC_WIDTH,
309 PROP_DOC_HEIGHT,
310 PROP_CAN_ZOOM_IN,
311 PROP_CAN_ZOOM_OUT,
312 PROP_DOC_PASSWORD,
313 PROP_DOC_PASSWORD_TO_MODIFY,
314 PROP_TILED_ANNOTATIONS,
316 PROP_LAST
319 static guint doc_view_signals[LAST_SIGNAL] = { 0 };
320 static GParamSpec *properties[PROP_LAST] = { nullptr };
322 static void lok_doc_view_initable_iface_init (GInitableIface *iface);
323 static void callbackWorker (int nType, const char* pPayload, void* pData);
324 static void updateClientZoom (LOKDocView *pDocView);
326 SAL_DLLPUBLIC_EXPORT GType lok_doc_view_get_type();
327 #ifdef __GNUC__
328 #pragma GCC diagnostic push
329 #pragma GCC diagnostic ignored "-Wunused-function"
330 #if defined __clang__
331 #if __has_warning("-Wdeprecated-volatile")
332 #pragma clang diagnostic ignored "-Wdeprecated-volatile"
333 #endif
334 #endif
335 #endif
336 G_DEFINE_TYPE_WITH_CODE (LOKDocView, lok_doc_view, GTK_TYPE_DRAWING_AREA,
337 G_ADD_PRIVATE (LOKDocView)
338 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, lok_doc_view_initable_iface_init));
339 #ifdef __GNUC__
340 #pragma GCC diagnostic pop
341 #endif
343 static LOKDocViewPrivate& getPrivate(LOKDocView* pDocView)
345 LOKDocViewPrivate* priv = static_cast<LOKDocViewPrivate*>(lok_doc_view_get_instance_private(pDocView));
346 return *priv;
349 namespace {
351 /// Helper struct used to pass the data from soffice thread -> main thread.
352 struct CallbackData
354 int m_nType;
355 std::string m_aPayload;
356 LOKDocView* m_pDocView;
358 CallbackData(int nType, const std::string& rPayload, LOKDocView* pDocView)
359 : m_nType(nType),
360 m_aPayload(rPayload),
361 m_pDocView(pDocView) {}
366 static void
367 LOKPostCommand (LOKDocView* pDocView,
368 const gchar* pCommand,
369 const gchar* pArguments,
370 bool bNotifyWhenFinished)
372 LOKDocViewPrivate& priv = getPrivate(pDocView);
373 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
374 LOEvent* pLOEvent = new LOEvent(LOK_POST_COMMAND);
375 GError* error = nullptr;
376 pLOEvent->m_pCommand = g_strdup(pCommand);
377 pLOEvent->m_pArguments = g_strdup(pArguments);
378 pLOEvent->m_bNotifyWhenFinished = bNotifyWhenFinished;
380 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
381 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
382 if (error != nullptr)
384 g_warning("Unable to call LOK_POST_COMMAND: %s", error->message);
385 g_clear_error(&error);
387 g_object_unref(task);
390 static void
391 doSearch(LOKDocView* pDocView, const char* pText, bool bBackwards, bool highlightAll)
393 LOKDocViewPrivate& priv = getPrivate(pDocView);
394 if (!priv->m_pDocument)
395 return;
397 boost::property_tree::ptree aTree;
398 GtkWidget* drawingWidget = GTK_WIDGET(pDocView);
399 GdkWindow* drawingWindow = gtk_widget_get_window(drawingWidget);
400 if (!drawingWindow)
401 return;
402 std::shared_ptr<cairo_region_t> cairoVisRegion( gdk_window_get_visible_region(drawingWindow),
403 cairo_region_destroy);
404 cairo_rectangle_int_t cairoVisRect;
405 cairo_region_get_rectangle(cairoVisRegion.get(), 0, &cairoVisRect);
406 int x = pixelToTwip (cairoVisRect.x, priv->m_fZoom);
407 int y = pixelToTwip (cairoVisRect.y, priv->m_fZoom);
409 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchString/type", '/'), "string");
410 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchString/value", '/'), pText);
411 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Backward/type", '/'), "boolean");
412 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Backward/value", '/'), bBackwards);
413 if (highlightAll)
415 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Command/type", '/'), "unsigned short");
416 // SvxSearchCmd::FIND_ALL
417 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Command/value", '/'), "1");
420 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointX/type", '/'), "long");
421 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointX/value", '/'), x);
422 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointY/type", '/'), "long");
423 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointY/value", '/'), y);
425 std::stringstream aStream;
426 boost::property_tree::write_json(aStream, aTree);
428 LOKPostCommand (pDocView, ".uno:ExecuteSearch", aStream.str().c_str(), false);
431 static bool
432 isEmptyRectangle(const GdkRectangle& rRectangle)
434 return rRectangle.x == 0 && rRectangle.y == 0 && rRectangle.width == 0 && rRectangle.height == 0;
437 /// if handled, returns TRUE else FALSE
438 static bool
439 handleTextSelectionOnButtonPress(GdkRectangle& aClick, LOKDocView* pDocView) {
440 LOKDocViewPrivate& priv = getPrivate(pDocView);
442 if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleStartRect, nullptr))
444 g_info("LOKDocView_Impl::signalButton: start of drag start handle");
445 priv->m_bInDragStartHandle = true;
446 return true;
448 else if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleMiddleRect, nullptr))
450 g_info("LOKDocView_Impl::signalButton: start of drag middle handle");
451 priv->m_bInDragMiddleHandle = true;
452 return true;
454 else if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleEndRect, nullptr))
456 g_info("LOKDocView_Impl::signalButton: start of drag end handle");
457 priv->m_bInDragEndHandle = true;
458 return true;
461 return false;
464 /// if handled, returns TRUE else FALSE
465 static bool
466 handleGraphicSelectionOnButtonPress(GdkRectangle& aClick, LOKDocView* pDocView) {
467 LOKDocViewPrivate& priv = getPrivate(pDocView);
468 GError* error = nullptr;
470 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
472 if (gdk_rectangle_intersect(&aClick, &priv->m_aGraphicHandleRects[i], nullptr))
474 g_info("LOKDocView_Impl::signalButton: start of drag graphic handle #%d", i);
475 priv->m_bInDragGraphicHandles[i] = true;
477 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
478 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
479 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_START;
480 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(priv->m_aGraphicHandleRects[i].x + priv->m_aGraphicHandleRects[i].width / 2, priv->m_fZoom);
481 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(priv->m_aGraphicHandleRects[i].y + priv->m_aGraphicHandleRects[i].height / 2, priv->m_fZoom);
482 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
484 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
485 if (error != nullptr)
487 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
488 g_clear_error(&error);
490 g_object_unref(task);
492 return true;
496 return false;
499 /// if handled, returns TRUE else FALSE
500 static bool
501 handleTextSelectionOnButtonRelease(LOKDocView* pDocView) {
502 LOKDocViewPrivate& priv = getPrivate(pDocView);
504 if (priv->m_bInDragStartHandle)
506 g_info("LOKDocView_Impl::signalButton: end of drag start handle");
507 priv->m_bInDragStartHandle = false;
508 return true;
510 else if (priv->m_bInDragMiddleHandle)
512 g_info("LOKDocView_Impl::signalButton: end of drag middle handle");
513 priv->m_bInDragMiddleHandle = false;
514 return true;
516 else if (priv->m_bInDragEndHandle)
518 g_info("LOKDocView_Impl::signalButton: end of drag end handle");
519 priv->m_bInDragEndHandle = false;
520 return true;
523 return false;
526 /// if handled, returns TRUE else FALSE
527 static bool
528 handleGraphicSelectionOnButtonRelease(LOKDocView* pDocView, GdkEventButton* pEvent) {
529 LOKDocViewPrivate& priv = getPrivate(pDocView);
530 GError* error = nullptr;
532 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
534 if (priv->m_bInDragGraphicHandles[i])
536 g_info("LOKDocView_Impl::signalButton: end of drag graphic handle #%d", i);
537 priv->m_bInDragGraphicHandles[i] = false;
539 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
540 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
541 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_END;
542 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(pEvent->x, priv->m_fZoom);
543 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(pEvent->y, priv->m_fZoom);
544 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
546 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
547 if (error != nullptr)
549 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
550 g_clear_error(&error);
552 g_object_unref(task);
554 return true;
558 if (priv->m_bInDragGraphicSelection)
560 g_info("LOKDocView_Impl::signalButton: end of drag graphic selection");
561 priv->m_bInDragGraphicSelection = false;
563 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
564 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
565 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_END;
566 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(pEvent->x, priv->m_fZoom);
567 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(pEvent->y, priv->m_fZoom);
568 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
570 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
571 if (error != nullptr)
573 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
574 g_clear_error(&error);
576 g_object_unref(task);
578 return true;
581 return false;
584 static void
585 postKeyEventInThread(gpointer data)
587 GTask* task = G_TASK(data);
588 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
589 LOKDocViewPrivate& priv = getPrivate(pDocView);
590 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
591 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
592 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
594 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
595 std::stringstream ss;
596 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
597 g_info("%s", ss.str().c_str());
598 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
600 if (priv->m_nTileSizeTwips)
602 ss.str(std::string());
603 ss << "lok::Document::setClientZoom(" << nTileSizePixelsScaled << ", " << nTileSizePixelsScaled << ", " << priv->m_nTileSizeTwips << ", " << priv->m_nTileSizeTwips << ")";
604 g_info("%s", ss.str().c_str());
605 priv->m_pDocument->pClass->setClientZoom(priv->m_pDocument,
606 nTileSizePixelsScaled,
607 nTileSizePixelsScaled,
608 priv->m_nTileSizeTwips,
609 priv->m_nTileSizeTwips);
610 priv->m_nTileSizeTwips = 0;
612 if (priv->m_bVisibleAreaSet)
614 ss.str(std::string());
615 ss << "lok::Document::setClientVisibleArea(" << priv->m_aVisibleArea.x << ", " << priv->m_aVisibleArea.y << ", ";
616 ss << priv->m_aVisibleArea.width << ", " << priv->m_aVisibleArea.height << ")";
617 g_info("%s", ss.str().c_str());
618 priv->m_pDocument->pClass->setClientVisibleArea(priv->m_pDocument,
619 priv->m_aVisibleArea.x,
620 priv->m_aVisibleArea.y,
621 priv->m_aVisibleArea.width,
622 priv->m_aVisibleArea.height);
623 priv->m_bVisibleAreaSet = false;
626 ss.str(std::string());
627 ss << "lok::Document::postKeyEvent(" << pLOEvent->m_nKeyEvent << ", " << pLOEvent->m_nCharCode << ", " << pLOEvent->m_nKeyCode << ")";
628 g_info("%s", ss.str().c_str());
629 priv->m_pDocument->pClass->postKeyEvent(priv->m_pDocument,
630 pLOEvent->m_nKeyEvent,
631 pLOEvent->m_nCharCode,
632 pLOEvent->m_nKeyCode);
635 static gboolean
636 signalKey (GtkWidget* pWidget, GdkEventKey* pEvent)
638 LOKDocView* pDocView = LOK_DOC_VIEW(pWidget);
639 LOKDocViewPrivate& priv = getPrivate(pDocView);
640 int nCharCode = 0;
641 int nKeyCode = 0;
642 GError* error = nullptr;
644 if (!priv->m_bEdit)
646 g_info("signalKey: not in edit mode, ignore");
647 return FALSE;
650 priv->m_nKeyModifier &= KEY_MOD2;
651 switch (pEvent->keyval)
653 case GDK_KEY_BackSpace:
654 nKeyCode = com::sun::star::awt::Key::BACKSPACE;
655 break;
656 case GDK_KEY_Delete:
657 nKeyCode = com::sun::star::awt::Key::DELETE;
658 break;
659 case GDK_KEY_Return:
660 case GDK_KEY_KP_Enter:
661 nKeyCode = com::sun::star::awt::Key::RETURN;
662 break;
663 case GDK_KEY_Escape:
664 nKeyCode = com::sun::star::awt::Key::ESCAPE;
665 break;
666 case GDK_KEY_Tab:
667 nKeyCode = com::sun::star::awt::Key::TAB;
668 break;
669 case GDK_KEY_Down:
670 nKeyCode = com::sun::star::awt::Key::DOWN;
671 break;
672 case GDK_KEY_Up:
673 nKeyCode = com::sun::star::awt::Key::UP;
674 break;
675 case GDK_KEY_Left:
676 nKeyCode = com::sun::star::awt::Key::LEFT;
677 break;
678 case GDK_KEY_Right:
679 nKeyCode = com::sun::star::awt::Key::RIGHT;
680 break;
681 case GDK_KEY_Page_Down:
682 nKeyCode = com::sun::star::awt::Key::PAGEDOWN;
683 break;
684 case GDK_KEY_Page_Up:
685 nKeyCode = com::sun::star::awt::Key::PAGEUP;
686 break;
687 case GDK_KEY_Insert:
688 nKeyCode = com::sun::star::awt::Key::INSERT;
689 break;
690 case GDK_KEY_Shift_L:
691 case GDK_KEY_Shift_R:
692 if (pEvent->type == GDK_KEY_PRESS)
693 priv->m_nKeyModifier |= KEY_SHIFT;
694 break;
695 case GDK_KEY_Control_L:
696 case GDK_KEY_Control_R:
697 if (pEvent->type == GDK_KEY_PRESS)
698 priv->m_nKeyModifier |= KEY_MOD1;
699 break;
700 case GDK_KEY_Alt_L:
701 case GDK_KEY_Alt_R:
702 if (pEvent->type == GDK_KEY_PRESS)
703 priv->m_nKeyModifier |= KEY_MOD2;
704 else
705 priv->m_nKeyModifier &= ~KEY_MOD2;
706 break;
707 default:
708 if (pEvent->keyval >= GDK_KEY_F1 && pEvent->keyval <= GDK_KEY_F26)
709 nKeyCode = com::sun::star::awt::Key::F1 + (pEvent->keyval - GDK_KEY_F1);
710 else
711 nCharCode = gdk_keyval_to_unicode(pEvent->keyval);
714 // rsc is not public API, but should be good enough for debugging purposes.
715 // If this is needed for real, then probably a new param of type
716 // css::awt::KeyModifier is needed in postKeyEvent().
717 if (pEvent->state & GDK_SHIFT_MASK)
718 nKeyCode |= KEY_SHIFT;
720 if (pEvent->state & GDK_CONTROL_MASK)
721 nKeyCode |= KEY_MOD1;
723 if (priv->m_nKeyModifier & KEY_MOD2)
724 nKeyCode |= KEY_MOD2;
726 if (nKeyCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2)) {
727 if (pEvent->keyval >= GDK_KEY_a && pEvent->keyval <= GDK_KEY_z)
729 nKeyCode |= 512 + (pEvent->keyval - GDK_KEY_a);
731 else if (pEvent->keyval >= GDK_KEY_A && pEvent->keyval <= GDK_KEY_Z) {
732 nKeyCode |= 512 + (pEvent->keyval - GDK_KEY_A);
734 else if (pEvent->keyval >= GDK_KEY_0 && pEvent->keyval <= GDK_KEY_9) {
735 nKeyCode |= 256 + (pEvent->keyval - GDK_KEY_0);
739 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
740 LOEvent* pLOEvent = new LOEvent(LOK_POST_KEY);
741 pLOEvent->m_nKeyEvent = pEvent->type == GDK_KEY_RELEASE ? LOK_KEYEVENT_KEYUP : LOK_KEYEVENT_KEYINPUT;
742 pLOEvent->m_nCharCode = nCharCode;
743 pLOEvent->m_nKeyCode = nKeyCode;
744 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
745 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
746 if (error != nullptr)
748 g_warning("Unable to call LOK_POST_KEY: %s", error->message);
749 g_clear_error(&error);
751 g_object_unref(task);
753 return FALSE;
756 static gboolean
757 handleTimeout (gpointer pData)
759 LOKDocView* pDocView = LOK_DOC_VIEW (pData);
760 LOKDocViewPrivate& priv = getPrivate(pDocView);
762 if (priv->m_bEdit)
764 if (priv->m_bCursorOverlayVisible)
765 priv->m_bCursorOverlayVisible = false;
766 else
767 priv->m_bCursorOverlayVisible = true;
768 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
771 return G_SOURCE_CONTINUE;
774 static void
775 commandChanged(LOKDocView* pDocView, const std::string& rString)
777 g_signal_emit(pDocView, doc_view_signals[COMMAND_CHANGED], 0, rString.c_str());
780 static void
781 searchNotFound(LOKDocView* pDocView, const std::string& rString)
783 g_signal_emit(pDocView, doc_view_signals[SEARCH_NOT_FOUND], 0, rString.c_str());
786 static void searchResultCount(LOKDocView* pDocView, const std::string& rString)
788 g_signal_emit(pDocView, doc_view_signals[SEARCH_RESULT_COUNT], 0, rString.c_str());
791 static void commandResult(LOKDocView* pDocView, const std::string& rString)
793 g_signal_emit(pDocView, doc_view_signals[COMMAND_RESULT], 0, rString.c_str());
796 static void addressChanged(LOKDocView* pDocView, const std::string& rString)
798 g_signal_emit(pDocView, doc_view_signals[ADDRESS_CHANGED], 0, rString.c_str());
801 static void formulaChanged(LOKDocView* pDocView, const std::string& rString)
803 g_signal_emit(pDocView, doc_view_signals[FORMULA_CHANGED], 0, rString.c_str());
806 static void reportError(LOKDocView* /*pDocView*/, const std::string& rString)
808 GtkWidget *dialog = gtk_message_dialog_new(nullptr,
809 GTK_DIALOG_DESTROY_WITH_PARENT,
810 GTK_MESSAGE_ERROR,
811 GTK_BUTTONS_CLOSE,
812 "%s",
813 rString.c_str());
814 gtk_dialog_run(GTK_DIALOG(dialog));
815 gtk_widget_destroy(dialog);
818 static void
819 setPart(LOKDocView* pDocView, const std::string& rString)
821 LOKDocViewPrivate& priv = getPrivate(pDocView);
822 priv->m_nPartId = std::stoi(rString);
823 g_signal_emit(pDocView, doc_view_signals[PART_CHANGED], 0, priv->m_nPartId);
826 static void
827 hyperlinkClicked(LOKDocView* pDocView, const std::string& rString)
829 g_signal_emit(pDocView, doc_view_signals[HYPERLINK_CLICKED], 0, rString.c_str());
832 /// Trigger a redraw, invoked on the main thread by other functions running in a thread.
833 static gboolean queueDraw(gpointer pData)
835 GtkWidget* pWidget = static_cast<GtkWidget*>(pData);
837 gtk_widget_queue_draw(pWidget);
839 return G_SOURCE_REMOVE;
842 /// Looks up the author string from initializeForRendering()'s rendering arguments.
843 static std::string getAuthorRenderingArgument(LOKDocViewPrivate& priv)
845 std::stringstream aStream;
846 aStream << priv->m_aRenderingArguments;
847 boost::property_tree::ptree aTree;
848 boost::property_tree::read_json(aStream, aTree);
849 std::string aRet;
850 for (const auto& rPair : aTree)
852 if (rPair.first == ".uno:Author")
854 aRet = rPair.second.get<std::string>("value");
855 break;
858 return aRet;
861 /// Author string <-> View ID map
862 static std::map<std::string, int> g_aAuthorViews;
864 static void refreshSize(LOKDocView* pDocView)
866 LOKDocViewPrivate& priv = getPrivate(pDocView);
868 priv->m_pDocument->pClass->getDocumentSize(priv->m_pDocument, &priv->m_nDocumentWidthTwips, &priv->m_nDocumentHeightTwips);
869 float zoom = priv->m_fZoom;
870 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
871 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
872 long nDocumentWidthTwips = priv->m_nDocumentWidthTwips;
873 long nDocumentHeightTwips = priv->m_nDocumentHeightTwips;
874 long nDocumentWidthPixels = twipToPixel(nDocumentWidthTwips, zoom);
875 long nDocumentHeightPixels = twipToPixel(nDocumentHeightTwips, zoom);
877 // Total number of columns in this document.
878 guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixelsScaled);
879 priv->m_pTileBuffer = std::make_unique<TileBuffer>(nColumns, nScaleFactor);
880 gtk_widget_set_size_request(GTK_WIDGET(pDocView),
881 nDocumentWidthPixels,
882 nDocumentHeightPixels);
885 /// Set up LOKDocView after the document is loaded, invoked on the main thread by openDocumentInThread() running in a thread.
886 static gboolean postDocumentLoad(gpointer pData)
888 LOKDocView* pLOKDocView = static_cast<LOKDocView*>(pData);
889 LOKDocViewPrivate& priv = getPrivate(pLOKDocView);
891 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
892 priv->m_pDocument->pClass->initializeForRendering(priv->m_pDocument, priv->m_aRenderingArguments.c_str());
893 // This returns the view id of the "current" view, but sadly if you load multiple documents that
894 // is apparently not a view showing the most recently loaded document. Not much we can do here,
895 // though. If that is fixed, this comment becomes incorrect.
896 priv->m_nViewId = priv->m_pDocument->pClass->getView(priv->m_pDocument);
897 g_aAuthorViews[getAuthorRenderingArgument(priv)] = priv->m_nViewId;
898 priv->m_pDocument->pClass->registerCallback(priv->m_pDocument, callbackWorker, pLOKDocView);
899 priv->m_nParts = priv->m_pDocument->pClass->getParts(priv->m_pDocument);
900 aGuard.unlock();
901 priv->m_nTimeoutId = g_timeout_add(600, handleTimeout, pLOKDocView);
903 refreshSize(pLOKDocView);
905 gtk_widget_set_can_focus(GTK_WIDGET(pLOKDocView), true);
906 gtk_widget_grab_focus(GTK_WIDGET(pLOKDocView));
907 lok_doc_view_set_zoom(pLOKDocView, 1.0);
909 // we are completely loaded
910 priv->m_bInit = true;
911 g_object_notify_by_pspec(G_OBJECT(pLOKDocView), properties[PROP_IS_INITIALIZED]);
913 return G_SOURCE_REMOVE;
916 /// Implementation of the global callback handler, invoked by globalCallback();
917 static gboolean
918 globalCallback (gpointer pData)
920 CallbackData* pCallback = static_cast<CallbackData*>(pData);
921 LOKDocViewPrivate& priv = getPrivate(pCallback->m_pDocView);
922 bool bModify = false;
924 switch (pCallback->m_nType)
926 case LOK_CALLBACK_STATUS_INDICATOR_START:
928 priv->m_nLoadProgress = 0.0;
929 g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, 0.0);
931 break;
932 case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
934 priv->m_nLoadProgress = static_cast<gdouble>(std::stoi(pCallback->m_aPayload)/100.0);
935 g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, priv->m_nLoadProgress);
937 break;
938 case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
940 priv->m_nLoadProgress = 1.0;
941 g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, 1.0);
943 break;
944 case LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY:
945 bModify = true;
946 [[fallthrough]];
947 case LOK_CALLBACK_DOCUMENT_PASSWORD:
949 char const*const pURL(pCallback->m_aPayload.c_str());
950 g_signal_emit (pCallback->m_pDocView, doc_view_signals[PASSWORD_REQUIRED], 0, pURL, bModify);
952 break;
953 case LOK_CALLBACK_ERROR:
955 reportError(pCallback->m_pDocView, pCallback->m_aPayload);
957 break;
958 case LOK_CALLBACK_SIGNATURE_STATUS:
960 // TODO
962 break;
963 default:
964 g_assert(false);
965 break;
967 delete pCallback;
969 return G_SOURCE_REMOVE;
972 static void
973 globalCallbackWorker(int nType, const char* pPayload, void* pData)
975 LOKDocView* pDocView = LOK_DOC_VIEW (pData);
977 CallbackData* pCallback = new CallbackData(nType, pPayload ? pPayload : "(nil)", pDocView);
978 g_info("LOKDocView_Impl::globalCallbackWorkerImpl: %s, '%s'", lokCallbackTypeToString(nType), pPayload);
979 gdk_threads_add_idle(globalCallback, pCallback);
982 static GdkRectangle
983 payloadToRectangle (LOKDocView* pDocView, const char* pPayload)
985 LOKDocViewPrivate& priv = getPrivate(pDocView);
986 GdkRectangle aRet;
987 // x, y, width, height, part number.
988 gchar** ppCoordinates = g_strsplit(pPayload, ", ", 5);
989 gchar** ppCoordinate = ppCoordinates;
991 aRet.width = aRet.height = aRet.x = aRet.y = 0;
993 if (!*ppCoordinate)
995 g_strfreev(ppCoordinates);
996 return aRet;
998 aRet.x = atoi(*ppCoordinate);
999 if (aRet.x < 0)
1000 aRet.x = 0;
1001 ++ppCoordinate;
1002 if (!*ppCoordinate)
1004 g_strfreev(ppCoordinates);
1005 return aRet;
1007 aRet.y = atoi(*ppCoordinate);
1008 if (aRet.y < 0)
1009 aRet.y = 0;
1010 ++ppCoordinate;
1011 if (!*ppCoordinate)
1013 g_strfreev(ppCoordinates);
1014 return aRet;
1016 long l = atol(*ppCoordinate);
1017 if (l > std::numeric_limits<int>::max())
1018 aRet.width = std::numeric_limits<int>::max();
1019 else
1020 aRet.width = l;
1021 if (aRet.x + aRet.width > priv->m_nDocumentWidthTwips)
1022 aRet.width = priv->m_nDocumentWidthTwips - aRet.x;
1023 ++ppCoordinate;
1024 if (!*ppCoordinate)
1026 g_strfreev(ppCoordinates);
1027 return aRet;
1029 l = atol(*ppCoordinate);
1030 if (l > std::numeric_limits<int>::max())
1031 aRet.height = std::numeric_limits<int>::max();
1032 else
1033 aRet.height = l;
1034 if (aRet.y + aRet.height > priv->m_nDocumentHeightTwips)
1035 aRet.height = priv->m_nDocumentHeightTwips - aRet.y;
1037 g_strfreev(ppCoordinates);
1038 return aRet;
1041 static std::vector<GdkRectangle>
1042 payloadToRectangles(LOKDocView* pDocView, const char* pPayload)
1044 std::vector<GdkRectangle> aRet;
1046 if (g_strcmp0(pPayload, "EMPTY") == 0)
1047 return aRet;
1049 gchar** ppRectangles = g_strsplit(pPayload, "; ", 0);
1050 for (gchar** ppRectangle = ppRectangles; *ppRectangle; ++ppRectangle)
1051 aRet.push_back(payloadToRectangle(pDocView, *ppRectangle));
1052 g_strfreev(ppRectangles);
1054 return aRet;
1058 static void
1059 setTilesInvalid (LOKDocView* pDocView, const GdkRectangle& rRectangle)
1061 LOKDocViewPrivate& priv = getPrivate(pDocView);
1062 GdkRectangle aRectanglePixels;
1063 GdkPoint aStart, aEnd;
1064 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
1065 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
1067 aRectanglePixels.x = twipToPixel(rRectangle.x, priv->m_fZoom) * nScaleFactor;
1068 aRectanglePixels.y = twipToPixel(rRectangle.y, priv->m_fZoom) * nScaleFactor;
1069 aRectanglePixels.width = twipToPixel(rRectangle.width, priv->m_fZoom) * nScaleFactor;
1070 aRectanglePixels.height = twipToPixel(rRectangle.height, priv->m_fZoom) * nScaleFactor;
1072 aStart.x = aRectanglePixels.y / nTileSizePixelsScaled;
1073 aStart.y = aRectanglePixels.x / nTileSizePixelsScaled;
1074 aEnd.x = (aRectanglePixels.y + aRectanglePixels.height + nTileSizePixelsScaled) / nTileSizePixelsScaled;
1075 aEnd.y = (aRectanglePixels.x + aRectanglePixels.width + nTileSizePixelsScaled) / nTileSizePixelsScaled;
1076 for (int i = aStart.x; i < aEnd.x; i++)
1078 for (int j = aStart.y; j < aEnd.y; j++)
1080 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
1081 priv->m_pTileBuffer->setInvalid(i, j, priv->m_fZoom, task, priv->lokThreadPool);
1082 g_object_unref(task);
1087 static gboolean
1088 callback (gpointer pData)
1090 CallbackData* pCallback = static_cast<CallbackData*>(pData);
1091 LOKDocView* pDocView = LOK_DOC_VIEW (pCallback->m_pDocView);
1092 LOKDocViewPrivate& priv = getPrivate(pDocView);
1094 //callback registered before the widget was destroyed.
1095 //Use existence of lokThreadPool as flag it was torn down
1096 if (!priv->lokThreadPool)
1098 delete pCallback;
1099 return G_SOURCE_REMOVE;
1102 switch (static_cast<LibreOfficeKitCallbackType>(pCallback->m_nType))
1104 case LOK_CALLBACK_INVALIDATE_TILES:
1106 if (pCallback->m_aPayload.compare(0, 5, "EMPTY") != 0) // payload doesn't start with "EMPTY"
1108 GdkRectangle aRectangle = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1109 setTilesInvalid(pDocView, aRectangle);
1111 else
1112 priv->m_pTileBuffer->resetAllTiles();
1114 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1116 break;
1117 case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
1120 std::stringstream aStream(pCallback->m_aPayload);
1121 boost::property_tree::ptree aTree;
1122 boost::property_tree::read_json(aStream, aTree);
1123 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1124 int nViewId = aTree.get<int>("viewId");
1126 priv->m_aVisibleCursor = payloadToRectangle(pDocView, rRectangle.c_str());
1127 priv->m_bCursorOverlayVisible = true;
1128 if(nViewId == priv->m_nViewId)
1130 g_signal_emit(pDocView, doc_view_signals[CURSOR_CHANGED], 0,
1131 priv->m_aVisibleCursor.x,
1132 priv->m_aVisibleCursor.y,
1133 priv->m_aVisibleCursor.width,
1134 priv->m_aVisibleCursor.height);
1136 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1138 break;
1139 case LOK_CALLBACK_TEXT_SELECTION:
1141 priv->m_aTextSelectionRectangles = payloadToRectangles(pDocView, pCallback->m_aPayload.c_str());
1142 bool bIsTextSelected = !priv->m_aTextSelectionRectangles.empty();
1143 // In case the selection is empty, then we get no LOK_CALLBACK_TEXT_SELECTION_START/END events.
1144 if (!bIsTextSelected)
1146 memset(&priv->m_aTextSelectionStart, 0, sizeof(priv->m_aTextSelectionStart));
1147 memset(&priv->m_aHandleStartRect, 0, sizeof(priv->m_aHandleStartRect));
1148 memset(&priv->m_aTextSelectionEnd, 0, sizeof(priv->m_aTextSelectionEnd));
1149 memset(&priv->m_aHandleEndRect, 0, sizeof(priv->m_aHandleEndRect));
1151 else
1152 memset(&priv->m_aHandleMiddleRect, 0, sizeof(priv->m_aHandleMiddleRect));
1154 g_signal_emit(pDocView, doc_view_signals[TEXT_SELECTION], 0, bIsTextSelected);
1155 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1157 break;
1158 case LOK_CALLBACK_TEXT_SELECTION_START:
1160 priv->m_aTextSelectionStart = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1162 break;
1163 case LOK_CALLBACK_TEXT_SELECTION_END:
1165 priv->m_aTextSelectionEnd = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1167 break;
1168 case LOK_CALLBACK_CURSOR_VISIBLE:
1170 priv->m_bCursorVisible = pCallback->m_aPayload == "true";
1172 break;
1173 case LOK_CALLBACK_MOUSE_POINTER:
1175 // We do not want the cursor to get changed in view-only mode
1176 if (priv->m_bEdit)
1178 // The gtk docs claim that most css cursors should be supported, however
1179 // on my system at least this is not true and many cursors are unsupported.
1180 // In this case pCursor = null, which results in the default cursor
1181 // being set.
1182 GdkCursor* pCursor = gdk_cursor_new_from_name(gtk_widget_get_display(GTK_WIDGET(pDocView)),
1183 pCallback->m_aPayload.c_str());
1184 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(pDocView)), pCursor);
1187 break;
1188 case LOK_CALLBACK_GRAPHIC_SELECTION:
1190 if (pCallback->m_aPayload != "EMPTY")
1191 priv->m_aGraphicSelection = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1192 else
1193 memset(&priv->m_aGraphicSelection, 0, sizeof(priv->m_aGraphicSelection));
1194 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1196 break;
1197 case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
1199 std::stringstream aStream(pCallback->m_aPayload);
1200 boost::property_tree::ptree aTree;
1201 boost::property_tree::read_json(aStream, aTree);
1202 int nViewId = aTree.get<int>("viewId");
1203 int nPart = aTree.get<int>("part");
1204 const std::string& rRectangle = aTree.get<std::string>("selection");
1205 if (rRectangle != "EMPTY")
1206 priv->m_aGraphicViewSelections[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1207 else
1209 auto it = priv->m_aGraphicViewSelections.find(nViewId);
1210 if (it != priv->m_aGraphicViewSelections.end())
1211 priv->m_aGraphicViewSelections.erase(it);
1213 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1214 break;
1216 break;
1217 case LOK_CALLBACK_CELL_CURSOR:
1219 if (pCallback->m_aPayload != "EMPTY")
1220 priv->m_aCellCursor = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1221 else
1222 memset(&priv->m_aCellCursor, 0, sizeof(priv->m_aCellCursor));
1223 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1225 break;
1226 case LOK_CALLBACK_HYPERLINK_CLICKED:
1228 hyperlinkClicked(pDocView, pCallback->m_aPayload);
1230 break;
1231 case LOK_CALLBACK_STATE_CHANGED:
1233 commandChanged(pDocView, pCallback->m_aPayload);
1235 break;
1236 case LOK_CALLBACK_SEARCH_NOT_FOUND:
1238 searchNotFound(pDocView, pCallback->m_aPayload);
1240 break;
1241 case LOK_CALLBACK_DOCUMENT_SIZE_CHANGED:
1243 refreshSize(pDocView);
1244 g_signal_emit(pDocView, doc_view_signals[SIZE_CHANGED], 0, nullptr);
1246 break;
1247 case LOK_CALLBACK_SET_PART:
1249 setPart(pDocView, pCallback->m_aPayload);
1251 break;
1252 case LOK_CALLBACK_SEARCH_RESULT_SELECTION:
1254 boost::property_tree::ptree aTree;
1255 std::stringstream aStream(pCallback->m_aPayload);
1256 boost::property_tree::read_json(aStream, aTree);
1257 int nCount = aTree.get_child("searchResultSelection").size();
1258 searchResultCount(pDocView, std::to_string(nCount));
1260 break;
1261 case LOK_CALLBACK_UNO_COMMAND_RESULT:
1263 commandResult(pDocView, pCallback->m_aPayload);
1265 break;
1266 case LOK_CALLBACK_CELL_ADDRESS:
1268 addressChanged(pDocView, pCallback->m_aPayload);
1270 break;
1271 case LOK_CALLBACK_CELL_FORMULA:
1273 formulaChanged(pDocView, pCallback->m_aPayload);
1275 break;
1276 case LOK_CALLBACK_ERROR:
1278 reportError(pDocView, pCallback->m_aPayload);
1280 break;
1281 case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
1283 std::stringstream aStream(pCallback->m_aPayload);
1284 boost::property_tree::ptree aTree;
1285 boost::property_tree::read_json(aStream, aTree);
1286 int nViewId = aTree.get<int>("viewId");
1287 int nPart = aTree.get<int>("part");
1288 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1289 priv->m_aViewCursors[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1290 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1291 break;
1293 case LOK_CALLBACK_TEXT_VIEW_SELECTION:
1295 std::stringstream aStream(pCallback->m_aPayload);
1296 boost::property_tree::ptree aTree;
1297 boost::property_tree::read_json(aStream, aTree);
1298 int nViewId = aTree.get<int>("viewId");
1299 int nPart = aTree.get<int>("part");
1300 const std::string& rSelection = aTree.get<std::string>("selection");
1301 priv->m_aTextViewSelectionRectangles[nViewId] = ViewRectangles(nPart, payloadToRectangles(pDocView, rSelection.c_str()));
1302 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1303 break;
1305 case LOK_CALLBACK_VIEW_CURSOR_VISIBLE:
1307 std::stringstream aStream(pCallback->m_aPayload);
1308 boost::property_tree::ptree aTree;
1309 boost::property_tree::read_json(aStream, aTree);
1310 int nViewId = aTree.get<int>("viewId");
1311 const std::string& rVisible = aTree.get<std::string>("visible");
1312 priv->m_aViewCursorVisibilities[nViewId] = rVisible == "true";
1313 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1314 break;
1316 break;
1317 case LOK_CALLBACK_CELL_VIEW_CURSOR:
1319 std::stringstream aStream(pCallback->m_aPayload);
1320 boost::property_tree::ptree aTree;
1321 boost::property_tree::read_json(aStream, aTree);
1322 int nViewId = aTree.get<int>("viewId");
1323 int nPart = aTree.get<int>("part");
1324 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1325 if (rRectangle != "EMPTY")
1326 priv->m_aCellViewCursors[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1327 else
1329 auto it = priv->m_aCellViewCursors.find(nViewId);
1330 if (it != priv->m_aCellViewCursors.end())
1331 priv->m_aCellViewCursors.erase(it);
1333 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1334 break;
1336 case LOK_CALLBACK_VIEW_LOCK:
1338 std::stringstream aStream(pCallback->m_aPayload);
1339 boost::property_tree::ptree aTree;
1340 boost::property_tree::read_json(aStream, aTree);
1341 int nViewId = aTree.get<int>("viewId");
1342 int nPart = aTree.get<int>("part");
1343 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1344 if (rRectangle != "EMPTY")
1345 priv->m_aViewLockRectangles[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1346 else
1348 auto it = priv->m_aViewLockRectangles.find(nViewId);
1349 if (it != priv->m_aViewLockRectangles.end())
1350 priv->m_aViewLockRectangles.erase(it);
1352 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1353 break;
1355 case LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED:
1357 break;
1359 case LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED:
1361 break;
1363 case LOK_CALLBACK_COMMENT:
1364 g_signal_emit(pCallback->m_pDocView, doc_view_signals[COMMENT], 0, pCallback->m_aPayload.c_str());
1365 break;
1366 case LOK_CALLBACK_RULER_UPDATE:
1367 g_signal_emit(pCallback->m_pDocView, doc_view_signals[RULER], 0, pCallback->m_aPayload.c_str());
1368 break;
1369 case LOK_CALLBACK_WINDOW:
1370 g_signal_emit(pCallback->m_pDocView, doc_view_signals[WINDOW], 0, pCallback->m_aPayload.c_str());
1371 break;
1372 case LOK_CALLBACK_INVALIDATE_HEADER:
1373 g_signal_emit(pCallback->m_pDocView, doc_view_signals[INVALIDATE_HEADER], 0, pCallback->m_aPayload.c_str());
1374 break;
1375 case LOK_CALLBACK_REFERENCE_MARKS:
1377 std::stringstream aStream(pCallback->m_aPayload);
1378 boost::property_tree::ptree aTree;
1379 boost::property_tree::read_json(aStream, aTree);
1381 priv->m_aReferenceMarks.clear();
1383 for(const auto& rMark : aTree.get_child("marks"))
1385 sal_uInt32 nColor = std::stoi(rMark.second.get<std::string>("color"), nullptr, 16);
1386 std::string sRect = rMark.second.get<std::string>("rectangle");
1387 sal_uInt32 nPart = std::stoi(rMark.second.get<std::string>("part"));
1389 GdkRectangle aRect = payloadToRectangle(pDocView, sRect.c_str());
1390 priv->m_aReferenceMarks.push_back(std::pair<ViewRectangle, sal_uInt32>(ViewRectangle(nPart, aRect), nColor));
1393 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1394 break;
1397 case LOK_CALLBACK_STATUS_INDICATOR_START:
1398 case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
1399 case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
1400 case LOK_CALLBACK_DOCUMENT_PASSWORD:
1401 case LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY:
1402 case LOK_CALLBACK_VALIDITY_LIST_BUTTON:
1403 case LOK_CALLBACK_VALIDITY_INPUT_HELP:
1404 case LOK_CALLBACK_SIGNATURE_STATUS:
1405 case LOK_CALLBACK_CONTEXT_MENU:
1406 case LOK_CALLBACK_PROFILE_FRAME:
1407 case LOK_CALLBACK_CLIPBOARD_CHANGED:
1408 case LOK_CALLBACK_CONTEXT_CHANGED:
1409 case LOK_CALLBACK_CELL_SELECTION_AREA:
1410 case LOK_CALLBACK_CELL_AUTO_FILL_AREA:
1411 case LOK_CALLBACK_TABLE_SELECTED:
1412 case LOK_CALLBACK_JSDIALOG:
1413 case LOK_CALLBACK_CALC_FUNCTION_LIST:
1414 case LOK_CALLBACK_TAB_STOP_LIST:
1415 case LOK_CALLBACK_FORM_FIELD_BUTTON:
1416 case LOK_CALLBACK_INVALIDATE_SHEET_GEOMETRY:
1418 // TODO: Implement me
1419 break;
1422 delete pCallback;
1424 return G_SOURCE_REMOVE;
1427 static void callbackWorker (int nType, const char* pPayload, void* pData)
1429 LOKDocView* pDocView = LOK_DOC_VIEW (pData);
1431 CallbackData* pCallback = new CallbackData(nType, pPayload ? pPayload : "(nil)", pDocView);
1432 LOKDocViewPrivate& priv = getPrivate(pDocView);
1433 std::stringstream ss;
1434 ss << "callbackWorker, view #" << priv->m_nViewId << ": " << lokCallbackTypeToString(nType) << ", '" << (pPayload ? pPayload : "(nil)") << "'";
1435 g_info("%s", ss.str().c_str());
1436 gdk_threads_add_idle(callback, pCallback);
1439 static void
1440 renderHandle(LOKDocView* pDocView,
1441 cairo_t* pCairo,
1442 const GdkRectangle& rCursor,
1443 cairo_surface_t* pHandle,
1444 GdkRectangle& rRectangle)
1446 LOKDocViewPrivate& priv = getPrivate(pDocView);
1447 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
1448 GdkPoint aCursorBottom;
1449 int nHandleWidth, nHandleHeight;
1450 double fHandleScale;
1452 nHandleWidth = cairo_image_surface_get_width(pHandle);
1453 nHandleHeight = cairo_image_surface_get_height(pHandle);
1454 // We want to scale down the handle, so that its height is the same as the cursor caret.
1455 fHandleScale = twipToPixel(rCursor.height, priv->m_fZoom) / nHandleHeight;
1456 // We want the top center of the handle bitmap to be at the bottom center of the cursor rectangle.
1457 aCursorBottom.x = twipToPixel(rCursor.x, priv->m_fZoom) + twipToPixel(rCursor.width, priv->m_fZoom) / 2 - (nHandleWidth * fHandleScale) / 2;
1458 aCursorBottom.y = twipToPixel(rCursor.y, priv->m_fZoom) + twipToPixel(rCursor.height, priv->m_fZoom);
1460 cairo_save (pCairo);
1461 cairo_scale(pCairo, 1.0 / nScaleFactor, 1.0 / nScaleFactor);
1462 cairo_translate(pCairo, aCursorBottom.x * nScaleFactor, aCursorBottom.y * nScaleFactor);
1463 cairo_scale(pCairo, fHandleScale * nScaleFactor, fHandleScale * nScaleFactor);
1464 cairo_set_source_surface(pCairo, pHandle, 0, 0);
1465 cairo_paint(pCairo);
1466 cairo_restore (pCairo);
1468 rRectangle.x = aCursorBottom.x;
1469 rRectangle.y = aCursorBottom.y;
1470 rRectangle.width = nHandleWidth * fHandleScale;
1471 rRectangle.height = nHandleHeight * fHandleScale;
1474 /// Renders handles around an rSelection rectangle on pCairo.
1475 static void
1476 renderGraphicHandle(LOKDocView* pDocView,
1477 cairo_t* pCairo,
1478 const GdkRectangle& rSelection,
1479 const GdkRGBA& rColor)
1481 LOKDocViewPrivate& priv = getPrivate(pDocView);
1482 int nHandleWidth = 9, nHandleHeight = 9;
1483 GdkRectangle aSelection;
1485 aSelection.x = twipToPixel(rSelection.x, priv->m_fZoom);
1486 aSelection.y = twipToPixel(rSelection.y, priv->m_fZoom);
1487 aSelection.width = twipToPixel(rSelection.width, priv->m_fZoom);
1488 aSelection.height = twipToPixel(rSelection.height, priv->m_fZoom);
1490 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
1492 int x = aSelection.x, y = aSelection.y;
1494 switch (i)
1496 case 0: // top-left
1497 break;
1498 case 1: // top-middle
1499 x += aSelection.width / 2;
1500 break;
1501 case 2: // top-right
1502 x += aSelection.width;
1503 break;
1504 case 3: // middle-left
1505 y += aSelection.height / 2;
1506 break;
1507 case 4: // middle-right
1508 x += aSelection.width;
1509 y += aSelection.height / 2;
1510 break;
1511 case 5: // bottom-left
1512 y += aSelection.height;
1513 break;
1514 case 6: // bottom-middle
1515 x += aSelection.width / 2;
1516 y += aSelection.height;
1517 break;
1518 case 7: // bottom-right
1519 x += aSelection.width;
1520 y += aSelection.height;
1521 break;
1524 // Center the handle.
1525 x -= nHandleWidth / 2;
1526 y -= nHandleHeight / 2;
1528 priv->m_aGraphicHandleRects[i].x = x;
1529 priv->m_aGraphicHandleRects[i].y = y;
1530 priv->m_aGraphicHandleRects[i].width = nHandleWidth;
1531 priv->m_aGraphicHandleRects[i].height = nHandleHeight;
1533 cairo_set_source_rgb(pCairo, rColor.red, rColor.green, rColor.blue);
1534 cairo_rectangle(pCairo, x, y, nHandleWidth, nHandleHeight);
1535 cairo_fill(pCairo);
1539 /// Finishes the paint tile operation and returns the result, if any
1540 static gpointer
1541 paintTileFinish(LOKDocView* pDocView, GAsyncResult* res, GError **error)
1543 GTask* task = G_TASK(res);
1545 g_return_val_if_fail(LOK_IS_DOC_VIEW(pDocView), nullptr);
1546 g_return_val_if_fail(g_task_is_valid(res, pDocView), nullptr);
1547 g_return_val_if_fail(error == nullptr || *error == nullptr, nullptr);
1549 return g_task_propagate_pointer(task, error);
1552 /// Callback called in the main UI thread when paintTileInThread in LOK thread has finished
1553 static void
1554 paintTileCallback(GObject* sourceObject, GAsyncResult* res, gpointer userData)
1556 LOKDocView* pDocView = LOK_DOC_VIEW(sourceObject);
1557 LOKDocViewPrivate& priv = getPrivate(pDocView);
1558 LOEvent* pLOEvent = static_cast<LOEvent*>(userData);
1559 std::unique_ptr<TileBuffer>& buffer = priv->m_pTileBuffer;
1560 GError* error;
1562 error = nullptr;
1563 cairo_surface_t* pSurface = static_cast<cairo_surface_t*>(paintTileFinish(pDocView, res, &error));
1564 if (error != nullptr)
1566 if (error->domain == LOK_TILEBUFFER_ERROR &&
1567 error->code == LOK_TILEBUFFER_CHANGED)
1568 g_info("Skipping paint tile request because corresponding"
1569 "tile buffer has been destroyed");
1570 else
1571 g_warning("Unable to get painted GdkPixbuf: %s", error->message);
1572 g_error_free(error);
1573 return;
1576 buffer->setTile(pLOEvent->m_nPaintTileX, pLOEvent->m_nPaintTileY, pSurface);
1577 gdk_threads_add_idle(queueDraw, GTK_WIDGET(pDocView));
1579 cairo_surface_destroy(pSurface);
1583 static bool
1584 renderDocument(LOKDocView* pDocView, cairo_t* pCairo)
1586 LOKDocViewPrivate& priv = getPrivate(pDocView);
1587 GdkRectangle aVisibleArea;
1588 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
1589 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
1590 long nDocumentWidthPixels = twipToPixel(priv->m_nDocumentWidthTwips, priv->m_fZoom) * nScaleFactor;
1591 long nDocumentHeightPixels = twipToPixel(priv->m_nDocumentHeightTwips, priv->m_fZoom) * nScaleFactor;
1592 // Total number of rows / columns in this document.
1593 guint nRows = ceil(static_cast<double>(nDocumentHeightPixels) / nTileSizePixelsScaled);
1594 guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixelsScaled);
1596 cairo_save (pCairo);
1597 cairo_scale (pCairo, 1.0/nScaleFactor, 1.0/nScaleFactor);
1598 gdk_cairo_get_clip_rectangle (pCairo, &aVisibleArea);
1599 aVisibleArea.x = pixelToTwip (aVisibleArea.x, priv->m_fZoom);
1600 aVisibleArea.y = pixelToTwip (aVisibleArea.y, priv->m_fZoom);
1601 aVisibleArea.width = pixelToTwip (aVisibleArea.width, priv->m_fZoom);
1602 aVisibleArea.height = pixelToTwip (aVisibleArea.height, priv->m_fZoom);
1604 // Render the tiles.
1605 for (guint nRow = 0; nRow < nRows; ++nRow)
1607 for (guint nColumn = 0; nColumn < nColumns; ++nColumn)
1609 GdkRectangle aTileRectangleTwips, aTileRectanglePixels;
1610 bool bPaint = true;
1612 // Determine size of the tile: the rightmost/bottommost tiles may
1613 // be smaller, and we need the size to decide if we need to repaint.
1614 if (nColumn == nColumns - 1)
1615 aTileRectanglePixels.width = nDocumentWidthPixels - nColumn * nTileSizePixelsScaled;
1616 else
1617 aTileRectanglePixels.width = nTileSizePixelsScaled;
1618 if (nRow == nRows - 1)
1619 aTileRectanglePixels.height = nDocumentHeightPixels - nRow * nTileSizePixelsScaled;
1620 else
1621 aTileRectanglePixels.height = nTileSizePixelsScaled;
1623 // Determine size and position of the tile in document coordinates,
1624 // so we can decide if we can skip painting for partial rendering.
1625 aTileRectangleTwips.x = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom) * nColumn;
1626 aTileRectangleTwips.y = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom) * nRow;
1627 aTileRectangleTwips.width = pixelToTwip(aTileRectanglePixels.width, priv->m_fZoom);
1628 aTileRectangleTwips.height = pixelToTwip(aTileRectanglePixels.height, priv->m_fZoom);
1630 if (!gdk_rectangle_intersect(&aVisibleArea, &aTileRectangleTwips, nullptr))
1631 bPaint = false;
1633 if (bPaint)
1635 LOEvent* pLOEvent = new LOEvent(LOK_PAINT_TILE);
1636 pLOEvent->m_nPaintTileX = nRow;
1637 pLOEvent->m_nPaintTileY = nColumn;
1638 pLOEvent->m_fPaintTileZoom = priv->m_fZoom;
1639 pLOEvent->m_pTileBuffer = &*priv->m_pTileBuffer;
1640 GTask* task = g_task_new(pDocView, nullptr, paintTileCallback, pLOEvent);
1641 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
1643 Tile& currentTile = priv->m_pTileBuffer->getTile(nRow, nColumn, task, priv->lokThreadPool);
1644 cairo_surface_t* pSurface = currentTile.getBuffer();
1645 cairo_set_source_surface(pCairo, pSurface,
1646 twipToPixel(aTileRectangleTwips.x, priv->m_fZoom),
1647 twipToPixel(aTileRectangleTwips.y, priv->m_fZoom));
1648 cairo_paint(pCairo);
1649 g_object_unref(task);
1654 cairo_restore (pCairo);
1655 return false;
1658 static const GdkRGBA& getDarkColor(int nViewId, LOKDocViewPrivate& priv)
1660 static std::map<int, GdkRGBA> aColorMap;
1661 auto it = aColorMap.find(nViewId);
1662 if (it != aColorMap.end())
1663 return it->second;
1665 if (priv->m_eDocumentType == LOK_DOCTYPE_TEXT)
1667 char* pValues = priv->m_pDocument->pClass->getCommandValues(priv->m_pDocument, ".uno:TrackedChangeAuthors");
1668 std::stringstream aInfo;
1669 aInfo << "lok::Document::getCommandValues('.uno:TrackedChangeAuthors') returned '" << pValues << "'" << std::endl;
1670 g_info("%s", aInfo.str().c_str());
1672 std::stringstream aStream(pValues);
1673 boost::property_tree::ptree aTree;
1674 boost::property_tree::read_json(aStream, aTree);
1675 for (const auto& rValue : aTree.get_child("authors"))
1677 const std::string& rName = rValue.second.get<std::string>("name");
1678 guint32 nColor = rValue.second.get<guint32>("color");
1679 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};
1680 auto itAuthorViews = g_aAuthorViews.find(rName);
1681 if (itAuthorViews != g_aAuthorViews.end())
1682 aColorMap[itAuthorViews->second] = aColor;
1685 else
1687 // Based on tools/color.hxx, COL_AUTHOR1_DARK..COL_AUTHOR9_DARK.
1688 static std::vector<GdkRGBA> aColors =
1690 {(double(198))/255, (double(146))/255, (double(0))/255, 0},
1691 {(double(6))/255, (double(70))/255, (double(162))/255, 0},
1692 {(double(87))/255, (double(157))/255, (double(28))/255, 0},
1693 {(double(105))/255, (double(43))/255, (double(157))/255, 0},
1694 {(double(197))/255, (double(0))/255, (double(11))/255, 0},
1695 {(double(0))/255, (double(128))/255, (double(128))/255, 0},
1696 {(double(140))/255, (double(132))/255, (double(0))/255, 0},
1697 {(double(43))/255, (double(85))/255, (double(107))/255, 0},
1698 {(double(209))/255, (double(118))/255, (double(0))/255, 0},
1700 static int nColorCounter = 0;
1701 GdkRGBA aColor = aColors[nColorCounter++ % aColors.size()];
1702 aColorMap[nViewId] = aColor;
1704 assert(aColorMap.find(nViewId) != aColorMap.end());
1705 return aColorMap[nViewId];
1708 static bool
1709 renderOverlay(LOKDocView* pDocView, cairo_t* pCairo)
1711 LOKDocViewPrivate& priv = getPrivate(pDocView);
1713 if (priv->m_bEdit && priv->m_bCursorVisible && priv->m_bCursorOverlayVisible && !isEmptyRectangle(priv->m_aVisibleCursor))
1715 if (priv->m_aVisibleCursor.width < 30)
1716 // Set a minimal width if it would be 0.
1717 priv->m_aVisibleCursor.width = 30;
1719 cairo_set_source_rgb(pCairo, 0, 0, 0);
1720 cairo_rectangle(pCairo,
1721 twipToPixel(priv->m_aVisibleCursor.x, priv->m_fZoom),
1722 twipToPixel(priv->m_aVisibleCursor.y, priv->m_fZoom),
1723 twipToPixel(priv->m_aVisibleCursor.width, priv->m_fZoom),
1724 twipToPixel(priv->m_aVisibleCursor.height, priv->m_fZoom));
1725 cairo_fill(pCairo);
1728 // View cursors: they do not blink and are colored.
1729 if (priv->m_bEdit && !priv->m_aViewCursors.empty())
1731 for (auto& rPair : priv->m_aViewCursors)
1733 auto itVisibility = priv->m_aViewCursorVisibilities.find(rPair.first);
1734 if (itVisibility != priv->m_aViewCursorVisibilities.end() && !itVisibility->second)
1735 continue;
1737 // Show view cursors when in Writer or when the part matches.
1738 if (rPair.second.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
1739 continue;
1741 GdkRectangle& rCursor = rPair.second.m_aRectangle;
1742 if (rCursor.width < 30)
1743 // Set a minimal width if it would be 0.
1744 rCursor.width = 30;
1746 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1747 cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
1748 cairo_rectangle(pCairo,
1749 twipToPixel(rCursor.x, priv->m_fZoom),
1750 twipToPixel(rCursor.y, priv->m_fZoom),
1751 twipToPixel(rCursor.width, priv->m_fZoom),
1752 twipToPixel(rCursor.height, priv->m_fZoom));
1753 cairo_fill(pCairo);
1757 if (priv->m_bEdit && priv->m_bCursorVisible && !isEmptyRectangle(priv->m_aVisibleCursor) && priv->m_aTextSelectionRectangles.empty())
1759 // Have a cursor, but no selection: we need the middle handle.
1760 gchar* handleMiddlePath = g_strconcat (priv->m_aLOPath.c_str(), CURSOR_HANDLE_DIR, "handle_image_middle.png", nullptr);
1761 if (!priv->m_pHandleMiddle)
1763 priv->m_pHandleMiddle = cairo_image_surface_create_from_png(handleMiddlePath);
1764 assert(cairo_surface_status(priv->m_pHandleMiddle) == CAIRO_STATUS_SUCCESS);
1766 g_free (handleMiddlePath);
1767 renderHandle(pDocView, pCairo, priv->m_aVisibleCursor, priv->m_pHandleMiddle, priv->m_aHandleMiddleRect);
1770 if (!priv->m_aTextSelectionRectangles.empty())
1772 for (const GdkRectangle& rRectangle : priv->m_aTextSelectionRectangles)
1774 // Blue with 75% transparency.
1775 cairo_set_source_rgba(pCairo, (double(0x43))/255, (double(0xac))/255, (double(0xe8))/255, 0.25);
1776 cairo_rectangle(pCairo,
1777 twipToPixel(rRectangle.x, priv->m_fZoom),
1778 twipToPixel(rRectangle.y, priv->m_fZoom),
1779 twipToPixel(rRectangle.width, priv->m_fZoom),
1780 twipToPixel(rRectangle.height, priv->m_fZoom));
1781 cairo_fill(pCairo);
1784 // Handles
1785 if (!isEmptyRectangle(priv->m_aTextSelectionStart))
1787 // Have a start position: we need a start handle.
1788 gchar* handleStartPath = g_strconcat (priv->m_aLOPath.c_str(), CURSOR_HANDLE_DIR, "handle_image_start.png", nullptr);
1789 if (!priv->m_pHandleStart)
1791 priv->m_pHandleStart = cairo_image_surface_create_from_png(handleStartPath);
1792 assert(cairo_surface_status(priv->m_pHandleStart) == CAIRO_STATUS_SUCCESS);
1794 renderHandle(pDocView, pCairo, priv->m_aTextSelectionStart, priv->m_pHandleStart, priv->m_aHandleStartRect);
1795 g_free (handleStartPath);
1797 if (!isEmptyRectangle(priv->m_aTextSelectionEnd))
1799 // Have a start position: we need an end handle.
1800 gchar* handleEndPath = g_strconcat (priv->m_aLOPath.c_str(), CURSOR_HANDLE_DIR, "handle_image_end.png", nullptr);
1801 if (!priv->m_pHandleEnd)
1803 priv->m_pHandleEnd = cairo_image_surface_create_from_png(handleEndPath);
1804 assert(cairo_surface_status(priv->m_pHandleEnd) == CAIRO_STATUS_SUCCESS);
1806 renderHandle(pDocView, pCairo, priv->m_aTextSelectionEnd, priv->m_pHandleEnd, priv->m_aHandleEndRect);
1807 g_free (handleEndPath);
1811 // Selections of other views.
1812 for (const auto& rPair : priv->m_aTextViewSelectionRectangles)
1814 if (rPair.second.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
1815 continue;
1817 for (const GdkRectangle& rRectangle : rPair.second.m_aRectangles)
1819 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1820 // 75% transparency.
1821 cairo_set_source_rgba(pCairo, rDark.red, rDark.green, rDark.blue, 0.25);
1822 cairo_rectangle(pCairo,
1823 twipToPixel(rRectangle.x, priv->m_fZoom),
1824 twipToPixel(rRectangle.y, priv->m_fZoom),
1825 twipToPixel(rRectangle.width, priv->m_fZoom),
1826 twipToPixel(rRectangle.height, priv->m_fZoom));
1827 cairo_fill(pCairo);
1831 if (!isEmptyRectangle(priv->m_aGraphicSelection))
1833 GdkRGBA const aBlack{0, 0, 0, 0};
1834 renderGraphicHandle(pDocView, pCairo, priv->m_aGraphicSelection, aBlack);
1837 // Graphic selections of other views.
1838 for (const auto& rPair : priv->m_aGraphicViewSelections)
1840 const ViewRectangle& rRectangle = rPair.second;
1841 if (rRectangle.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
1842 continue;
1844 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1845 renderGraphicHandle(pDocView, pCairo, rRectangle.m_aRectangle, rDark);
1848 // Draw the cell cursor.
1849 if (!isEmptyRectangle(priv->m_aCellCursor))
1851 cairo_set_source_rgb(pCairo, 0, 0, 0);
1852 cairo_rectangle(pCairo,
1853 twipToPixel(priv->m_aCellCursor.x, priv->m_fZoom),
1854 twipToPixel(priv->m_aCellCursor.y, priv->m_fZoom),
1855 twipToPixel(priv->m_aCellCursor.width, priv->m_fZoom),
1856 twipToPixel(priv->m_aCellCursor.height, priv->m_fZoom));
1857 cairo_set_line_width(pCairo, 2.0);
1858 cairo_stroke(pCairo);
1861 // Cell view cursors: they are colored.
1862 for (const auto& rPair : priv->m_aCellViewCursors)
1864 const ViewRectangle& rCursor = rPair.second;
1865 if (rCursor.m_nPart != priv->m_nPartId)
1866 continue;
1868 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1869 cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
1870 cairo_rectangle(pCairo,
1871 twipToPixel(rCursor.m_aRectangle.x, priv->m_fZoom),
1872 twipToPixel(rCursor.m_aRectangle.y, priv->m_fZoom),
1873 twipToPixel(rCursor.m_aRectangle.width, priv->m_fZoom),
1874 twipToPixel(rCursor.m_aRectangle.height, priv->m_fZoom));
1875 cairo_set_line_width(pCairo, 2.0);
1876 cairo_stroke(pCairo);
1879 // Draw reference marks.
1880 for (const auto& rPair : priv->m_aReferenceMarks)
1882 const ViewRectangle& rMark = rPair.first;
1883 if (rMark.m_nPart != priv->m_nPartId)
1884 continue;
1886 sal_uInt32 nColor = rPair.second;
1887 sal_uInt8 nRed = (nColor >> 16) & 0xff;
1888 sal_uInt8 nGreen = (nColor >> 8) & 0xff;
1889 sal_uInt8 nBlue = nColor & 0xff;
1890 cairo_set_source_rgb(pCairo, nRed, nGreen, nBlue);
1891 cairo_rectangle(pCairo,
1892 twipToPixel(rMark.m_aRectangle.x, priv->m_fZoom),
1893 twipToPixel(rMark.m_aRectangle.y, priv->m_fZoom),
1894 twipToPixel(rMark.m_aRectangle.width, priv->m_fZoom),
1895 twipToPixel(rMark.m_aRectangle.height, priv->m_fZoom));
1896 cairo_set_line_width(pCairo, 2.0);
1897 cairo_stroke(pCairo);
1900 // View locks: they are colored.
1901 for (const auto& rPair : priv->m_aViewLockRectangles)
1903 const ViewRectangle& rRectangle = rPair.second;
1904 if (rRectangle.m_nPart != priv->m_nPartId)
1905 continue;
1907 // Draw a rectangle.
1908 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1909 cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
1910 cairo_rectangle(pCairo,
1911 twipToPixel(rRectangle.m_aRectangle.x, priv->m_fZoom),
1912 twipToPixel(rRectangle.m_aRectangle.y, priv->m_fZoom),
1913 twipToPixel(rRectangle.m_aRectangle.width, priv->m_fZoom),
1914 twipToPixel(rRectangle.m_aRectangle.height, priv->m_fZoom));
1915 cairo_set_line_width(pCairo, 2.0);
1916 cairo_stroke(pCairo);
1918 // And a lock.
1919 cairo_rectangle(pCairo,
1920 twipToPixel(rRectangle.m_aRectangle.x + rRectangle.m_aRectangle.width, priv->m_fZoom) - 25,
1921 twipToPixel(rRectangle.m_aRectangle.y + rRectangle.m_aRectangle.height, priv->m_fZoom) - 15,
1923 10);
1924 cairo_fill(pCairo);
1925 cairo_arc(pCairo,
1926 twipToPixel(rRectangle.m_aRectangle.x + rRectangle.m_aRectangle.width, priv->m_fZoom) - 15,
1927 twipToPixel(rRectangle.m_aRectangle.y + rRectangle.m_aRectangle.height, priv->m_fZoom) - 15,
1929 180.0 * (M_PI/180.0),
1930 360.0 * (M_PI/180.0));
1931 cairo_stroke(pCairo);
1934 return false;
1937 static gboolean
1938 lok_doc_view_signal_button(GtkWidget* pWidget, GdkEventButton* pEvent)
1940 LOKDocView* pDocView = LOK_DOC_VIEW (pWidget);
1941 LOKDocViewPrivate& priv = getPrivate(pDocView);
1942 GError* error = nullptr;
1944 g_info("LOKDocView_Impl::signalButton: %d, %d (in twips: %d, %d)",
1945 static_cast<int>(pEvent->x), static_cast<int>(pEvent->y),
1946 static_cast<int>(pixelToTwip(pEvent->x, priv->m_fZoom)),
1947 static_cast<int>(pixelToTwip(pEvent->y, priv->m_fZoom)));
1948 gtk_widget_grab_focus(GTK_WIDGET(pDocView));
1950 switch (pEvent->type)
1952 case GDK_BUTTON_PRESS:
1954 GdkRectangle aClick;
1955 aClick.x = pEvent->x;
1956 aClick.y = pEvent->y;
1957 aClick.width = 1;
1958 aClick.height = 1;
1960 if (handleTextSelectionOnButtonPress(aClick, pDocView))
1961 return FALSE;
1962 if (handleGraphicSelectionOnButtonPress(aClick, pDocView))
1963 return FALSE;
1965 int nCount = 1;
1966 if ((pEvent->time - priv->m_nLastButtonPressTime) < 250)
1967 nCount++;
1968 priv->m_nLastButtonPressTime = pEvent->time;
1969 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
1970 LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
1971 pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEBUTTONDOWN;
1972 pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
1973 pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
1974 pLOEvent->m_nPostMouseEventCount = nCount;
1975 switch (pEvent->button)
1977 case 1:
1978 pLOEvent->m_nPostMouseEventButton = MOUSE_LEFT;
1979 break;
1980 case 2:
1981 pLOEvent->m_nPostMouseEventButton = MOUSE_MIDDLE;
1982 break;
1983 case 3:
1984 pLOEvent->m_nPostMouseEventButton = MOUSE_RIGHT;
1985 break;
1987 pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
1988 priv->m_nLastButtonPressed = pLOEvent->m_nPostMouseEventButton;
1989 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
1991 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
1992 if (error != nullptr)
1994 g_warning("Unable to call LOK_POST_MOUSE_EVENT: %s", error->message);
1995 g_clear_error(&error);
1997 g_object_unref(task);
1998 break;
2000 case GDK_BUTTON_RELEASE:
2002 if (handleTextSelectionOnButtonRelease(pDocView))
2003 return FALSE;
2004 if (handleGraphicSelectionOnButtonRelease(pDocView, pEvent))
2005 return FALSE;
2007 int nCount = 1;
2008 if ((pEvent->time - priv->m_nLastButtonReleaseTime) < 250)
2009 nCount++;
2010 priv->m_nLastButtonReleaseTime = pEvent->time;
2011 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2012 LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
2013 pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEBUTTONUP;
2014 pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
2015 pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
2016 pLOEvent->m_nPostMouseEventCount = nCount;
2017 switch (pEvent->button)
2019 case 1:
2020 pLOEvent->m_nPostMouseEventButton = MOUSE_LEFT;
2021 break;
2022 case 2:
2023 pLOEvent->m_nPostMouseEventButton = MOUSE_MIDDLE;
2024 break;
2025 case 3:
2026 pLOEvent->m_nPostMouseEventButton = MOUSE_RIGHT;
2027 break;
2029 pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
2030 priv->m_nLastButtonPressed = pLOEvent->m_nPostMouseEventButton;
2031 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2033 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2034 if (error != nullptr)
2036 g_warning("Unable to call LOK_POST_MOUSE_EVENT: %s", error->message);
2037 g_clear_error(&error);
2039 g_object_unref(task);
2040 break;
2042 default:
2043 break;
2045 return FALSE;
2048 static void
2049 getDragPoint(GdkRectangle* pHandle,
2050 GdkEventMotion* pEvent,
2051 GdkPoint* pPoint)
2053 GdkPoint aCursor, aHandle;
2055 // Center of the cursor rectangle: we know that it's above the handle.
2056 aCursor.x = pHandle->x + pHandle->width / 2;
2057 aCursor.y = pHandle->y - pHandle->height / 2;
2058 // Center of the handle rectangle.
2059 aHandle.x = pHandle->x + pHandle->width / 2;
2060 aHandle.y = pHandle->y + pHandle->height / 2;
2061 // Our target is the original cursor position + the dragged offset.
2062 pPoint->x = aCursor.x + (pEvent->x - aHandle.x);
2063 pPoint->y = aCursor.y + (pEvent->y - aHandle.y);
2066 static gboolean
2067 lok_doc_view_signal_motion (GtkWidget* pWidget, GdkEventMotion* pEvent)
2069 LOKDocView* pDocView = LOK_DOC_VIEW (pWidget);
2070 LOKDocViewPrivate& priv = getPrivate(pDocView);
2071 GdkPoint aPoint;
2072 GError* error = nullptr;
2074 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2075 std::stringstream ss;
2076 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2077 g_info("%s", ss.str().c_str());
2078 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2079 if (priv->m_bInDragMiddleHandle)
2081 g_info("lcl_signalMotion: dragging the middle handle");
2082 getDragPoint(&priv->m_aHandleMiddleRect, pEvent, &aPoint);
2083 priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_RESET, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
2084 return FALSE;
2086 if (priv->m_bInDragStartHandle)
2088 g_info("lcl_signalMotion: dragging the start handle");
2089 getDragPoint(&priv->m_aHandleStartRect, pEvent, &aPoint);
2090 priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_START, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
2091 return FALSE;
2093 if (priv->m_bInDragEndHandle)
2095 g_info("lcl_signalMotion: dragging the end handle");
2096 getDragPoint(&priv->m_aHandleEndRect, pEvent, &aPoint);
2097 priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_END, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
2098 return FALSE;
2100 aGuard.unlock();
2101 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
2103 if (priv->m_bInDragGraphicHandles[i])
2105 g_info("lcl_signalMotion: dragging the graphic handle #%d", i);
2106 return FALSE;
2109 if (priv->m_bInDragGraphicSelection)
2111 g_info("lcl_signalMotion: dragging the graphic selection");
2112 return FALSE;
2115 GdkRectangle aMotionInTwipsInTwips;
2116 aMotionInTwipsInTwips.x = pixelToTwip(pEvent->x, priv->m_fZoom);
2117 aMotionInTwipsInTwips.y = pixelToTwip(pEvent->y, priv->m_fZoom);
2118 aMotionInTwipsInTwips.width = 1;
2119 aMotionInTwipsInTwips.height = 1;
2120 if (gdk_rectangle_intersect(&aMotionInTwipsInTwips, &priv->m_aGraphicSelection, nullptr))
2122 g_info("lcl_signalMotion: start of drag graphic selection");
2123 priv->m_bInDragGraphicSelection = true;
2125 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2126 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
2127 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_START;
2128 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(pEvent->x, priv->m_fZoom);
2129 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(pEvent->y, priv->m_fZoom);
2130 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2132 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2133 if (error != nullptr)
2135 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
2136 g_clear_error(&error);
2138 g_object_unref(task);
2140 return FALSE;
2143 // Otherwise a mouse move, as on the desktop.
2145 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2146 LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
2147 pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEMOVE;
2148 pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
2149 pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
2150 pLOEvent->m_nPostMouseEventCount = 1;
2151 pLOEvent->m_nPostMouseEventButton = priv->m_nLastButtonPressed;
2152 pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
2154 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2156 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2157 if (error != nullptr)
2159 g_warning("Unable to call LOK_MOUSEEVENT_MOUSEMOVE: %s", error->message);
2160 g_clear_error(&error);
2162 g_object_unref(task);
2164 return FALSE;
2167 static void
2168 setGraphicSelectionInThread(gpointer data)
2170 GTask* task = G_TASK(data);
2171 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2172 LOKDocViewPrivate& priv = getPrivate(pDocView);
2173 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2175 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2176 std::stringstream ss;
2177 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2178 g_info("%s", ss.str().c_str());
2179 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2180 ss.str(std::string());
2181 ss << "lok::Document::setGraphicSelection(" << pLOEvent->m_nSetGraphicSelectionType;
2182 ss << ", " << pLOEvent->m_nSetGraphicSelectionX;
2183 ss << ", " << pLOEvent->m_nSetGraphicSelectionY << ")";
2184 g_info("%s", ss.str().c_str());
2185 priv->m_pDocument->pClass->setGraphicSelection(priv->m_pDocument,
2186 pLOEvent->m_nSetGraphicSelectionType,
2187 pLOEvent->m_nSetGraphicSelectionX,
2188 pLOEvent->m_nSetGraphicSelectionY);
2191 static void
2192 setClientZoomInThread(gpointer data)
2194 GTask* task = G_TASK(data);
2195 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2196 LOKDocViewPrivate& priv = getPrivate(pDocView);
2197 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2199 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2200 priv->m_pDocument->pClass->setClientZoom(priv->m_pDocument,
2201 pLOEvent->m_nTilePixelWidth,
2202 pLOEvent->m_nTilePixelHeight,
2203 pLOEvent->m_nTileTwipWidth,
2204 pLOEvent->m_nTileTwipHeight);
2207 static void
2208 postMouseEventInThread(gpointer data)
2210 GTask* task = G_TASK(data);
2211 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2212 LOKDocViewPrivate& priv = getPrivate(pDocView);
2213 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2215 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2216 std::stringstream ss;
2217 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2218 g_info("%s", ss.str().c_str());
2219 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2220 ss.str(std::string());
2221 ss << "lok::Document::postMouseEvent(" << pLOEvent->m_nPostMouseEventType;
2222 ss << ", " << pLOEvent->m_nPostMouseEventX;
2223 ss << ", " << pLOEvent->m_nPostMouseEventY;
2224 ss << ", " << pLOEvent->m_nPostMouseEventCount;
2225 ss << ", " << pLOEvent->m_nPostMouseEventButton;
2226 ss << ", " << pLOEvent->m_nPostMouseEventModifier << ")";
2227 g_info("%s", ss.str().c_str());
2228 priv->m_pDocument->pClass->postMouseEvent(priv->m_pDocument,
2229 pLOEvent->m_nPostMouseEventType,
2230 pLOEvent->m_nPostMouseEventX,
2231 pLOEvent->m_nPostMouseEventY,
2232 pLOEvent->m_nPostMouseEventCount,
2233 pLOEvent->m_nPostMouseEventButton,
2234 pLOEvent->m_nPostMouseEventModifier);
2237 static void
2238 openDocumentInThread (gpointer data)
2240 GTask* task = G_TASK(data);
2241 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2242 LOKDocViewPrivate& priv = getPrivate(pDocView);
2244 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2245 if ( priv->m_pDocument )
2247 priv->m_pDocument->pClass->destroy( priv->m_pDocument );
2248 priv->m_pDocument = nullptr;
2251 priv->m_pOffice->pClass->registerCallback(priv->m_pOffice, globalCallbackWorker, pDocView);
2252 priv->m_pDocument = priv->m_pOffice->pClass->documentLoadWithOptions( priv->m_pOffice, priv->m_aDocPath.c_str(), "en-US" );
2253 if ( !priv->m_pDocument )
2255 char *pError = priv->m_pOffice->pClass->getError( priv->m_pOffice );
2256 g_task_return_new_error(task, g_quark_from_static_string ("LOK error"), 0, "%s", pError);
2258 else
2260 priv->m_eDocumentType = static_cast<LibreOfficeKitDocumentType>(priv->m_pDocument->pClass->getDocumentType(priv->m_pDocument));
2261 gdk_threads_add_idle(postDocumentLoad, pDocView);
2262 g_task_return_boolean (task, true);
2266 static void
2267 setPartInThread(gpointer data)
2269 GTask* task = G_TASK(data);
2270 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2271 LOKDocViewPrivate& priv = getPrivate(pDocView);
2272 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2273 int nPart = pLOEvent->m_nPart;
2275 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2276 std::stringstream ss;
2277 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2278 g_info("%s", ss.str().c_str());
2279 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2280 priv->m_pDocument->pClass->setPart( priv->m_pDocument, nPart );
2281 aGuard.unlock();
2283 lok_doc_view_reset_view(pDocView);
2286 static void
2287 setPartmodeInThread(gpointer data)
2289 GTask* task = G_TASK(data);
2290 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2291 LOKDocViewPrivate& priv = getPrivate(pDocView);
2292 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2293 int nPartMode = pLOEvent->m_nPartMode;
2295 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2296 std::stringstream ss;
2297 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2298 g_info("%s", ss.str().c_str());
2299 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2300 priv->m_pDocument->pClass->setPartMode( priv->m_pDocument, nPartMode );
2303 static void
2304 setEditInThread(gpointer data)
2306 GTask* task = G_TASK(data);
2307 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2308 LOKDocViewPrivate& priv = getPrivate(pDocView);
2309 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2310 bool bWasEdit = priv->m_bEdit;
2311 bool bEdit = pLOEvent->m_bEdit;
2313 if (!priv->m_bEdit && bEdit)
2314 g_info("lok_doc_view_set_edit: entering edit mode");
2315 else if (priv->m_bEdit && !bEdit)
2317 g_info("lok_doc_view_set_edit: leaving edit mode");
2318 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2319 std::stringstream ss;
2320 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2321 g_info("%s", ss.str().c_str());
2322 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2323 priv->m_pDocument->pClass->resetSelection(priv->m_pDocument);
2325 priv->m_bEdit = bEdit;
2326 g_signal_emit(pDocView, doc_view_signals[EDIT_CHANGED], 0, bWasEdit);
2327 gdk_threads_add_idle(queueDraw, GTK_WIDGET(pDocView));
2330 static void
2331 postCommandInThread (gpointer data)
2333 GTask* task = G_TASK(data);
2334 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2335 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2336 LOKDocViewPrivate& priv = getPrivate(pDocView);
2338 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2339 std::stringstream ss;
2340 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2341 g_info("%s", ss.str().c_str());
2342 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2343 ss.str(std::string());
2344 ss << "lok::Document::postUnoCommand(" << pLOEvent->m_pCommand << ", " << pLOEvent->m_pArguments << ")";
2345 g_info("%s", ss.str().c_str());
2346 priv->m_pDocument->pClass->postUnoCommand(priv->m_pDocument, pLOEvent->m_pCommand, pLOEvent->m_pArguments, pLOEvent->m_bNotifyWhenFinished);
2349 static void
2350 paintTileInThread (gpointer data)
2352 GTask* task = G_TASK(data);
2353 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2354 LOKDocViewPrivate& priv = getPrivate(pDocView);
2355 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2356 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
2357 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
2359 // check if "source" tile buffer is different from "current" tile buffer
2360 if (pLOEvent->m_pTileBuffer != &*priv->m_pTileBuffer)
2362 pLOEvent->m_pTileBuffer = nullptr;
2363 g_task_return_new_error(task,
2364 LOK_TILEBUFFER_ERROR,
2365 LOK_TILEBUFFER_CHANGED,
2366 "TileBuffer has changed");
2367 return;
2369 std::unique_ptr<TileBuffer>& buffer = priv->m_pTileBuffer;
2370 if (buffer->hasValidTile(pLOEvent->m_nPaintTileX, pLOEvent->m_nPaintTileY))
2371 return;
2373 cairo_surface_t *pSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nTileSizePixelsScaled, nTileSizePixelsScaled);
2374 if (cairo_surface_status(pSurface) != CAIRO_STATUS_SUCCESS)
2376 cairo_surface_destroy(pSurface);
2377 g_task_return_new_error(task,
2378 LOK_TILEBUFFER_ERROR,
2379 LOK_TILEBUFFER_MEMORY,
2380 "Error allocating Surface");
2381 return;
2384 unsigned char* pBuffer = cairo_image_surface_get_data(pSurface);
2385 GdkRectangle aTileRectangle;
2386 aTileRectangle.x = pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor) * pLOEvent->m_nPaintTileY;
2387 aTileRectangle.y = pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor) * pLOEvent->m_nPaintTileX;
2389 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2390 std::stringstream ss;
2391 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2392 g_info("%s", ss.str().c_str());
2393 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2394 ss.str(std::string());
2395 GTimer* aTimer = g_timer_new();
2396 gulong nElapsedMs;
2397 ss << "lok::Document::paintTile(" << static_cast<void*>(pBuffer) << ", "
2398 << nTileSizePixelsScaled << ", " << nTileSizePixelsScaled << ", "
2399 << aTileRectangle.x << ", " << aTileRectangle.y << ", "
2400 << pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor) << ", "
2401 << pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor) << ")";
2403 priv->m_pDocument->pClass->paintTile(priv->m_pDocument,
2404 pBuffer,
2405 nTileSizePixelsScaled, nTileSizePixelsScaled,
2406 aTileRectangle.x, aTileRectangle.y,
2407 pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor),
2408 pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor));
2409 aGuard.unlock();
2411 g_timer_elapsed(aTimer, &nElapsedMs);
2412 ss << " rendered in " << (nElapsedMs / 1000.) << " milliseconds";
2413 g_info("%s", ss.str().c_str());
2414 g_timer_destroy(aTimer);
2416 cairo_surface_mark_dirty(pSurface);
2418 // Its likely that while the tilebuffer has changed, one of the paint tile
2419 // requests has passed the previous check at start of this function, and has
2420 // rendered the tile already. We want to stop such rendered tiles from being
2421 // stored in new tile buffer.
2422 if (pLOEvent->m_pTileBuffer != &*priv->m_pTileBuffer)
2424 pLOEvent->m_pTileBuffer = nullptr;
2425 g_task_return_new_error(task,
2426 LOK_TILEBUFFER_ERROR,
2427 LOK_TILEBUFFER_CHANGED,
2428 "TileBuffer has changed");
2429 return;
2432 g_task_return_pointer(task, pSurface, reinterpret_cast<GDestroyNotify>(cairo_surface_destroy));
2436 static void
2437 lokThreadFunc(gpointer data, gpointer /*user_data*/)
2439 GTask* task = G_TASK(data);
2440 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2441 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2442 LOKDocViewPrivate& priv = getPrivate(pDocView);
2444 switch (pLOEvent->m_nType)
2446 case LOK_LOAD_DOC:
2447 openDocumentInThread(task);
2448 break;
2449 case LOK_POST_COMMAND:
2450 postCommandInThread(task);
2451 break;
2452 case LOK_SET_EDIT:
2453 setEditInThread(task);
2454 break;
2455 case LOK_SET_PART:
2456 setPartInThread(task);
2457 break;
2458 case LOK_SET_PARTMODE:
2459 setPartmodeInThread(task);
2460 break;
2461 case LOK_POST_KEY:
2462 // view-only/editable mode already checked during signal key signal emission
2463 postKeyEventInThread(task);
2464 break;
2465 case LOK_PAINT_TILE:
2466 paintTileInThread(task);
2467 break;
2468 case LOK_POST_MOUSE_EVENT:
2469 postMouseEventInThread(task);
2470 break;
2471 case LOK_SET_GRAPHIC_SELECTION:
2472 if (priv->m_bEdit)
2473 setGraphicSelectionInThread(task);
2474 else
2475 g_info ("LOK_SET_GRAPHIC_SELECTION: skipping graphical operation in view-only mode");
2476 break;
2477 case LOK_SET_CLIENT_ZOOM:
2478 setClientZoomInThread(task);
2479 break;
2482 g_object_unref(task);
2485 static void
2486 onStyleContextChanged (LOKDocView* pDocView)
2488 // The scale factor might have changed
2489 updateClientZoom (pDocView);
2490 gtk_widget_queue_draw (GTK_WIDGET (pDocView));
2493 static void lok_doc_view_init (LOKDocView* pDocView)
2495 LOKDocViewPrivate& priv = getPrivate(pDocView);
2496 priv.m_pImpl = new LOKDocViewPrivateImpl();
2498 gtk_widget_add_events(GTK_WIDGET(pDocView),
2499 GDK_BUTTON_PRESS_MASK
2500 |GDK_BUTTON_RELEASE_MASK
2501 |GDK_BUTTON_MOTION_MASK
2502 |GDK_KEY_PRESS_MASK
2503 |GDK_KEY_RELEASE_MASK);
2505 priv->lokThreadPool = g_thread_pool_new(lokThreadFunc,
2506 nullptr,
2508 FALSE,
2509 nullptr);
2511 g_signal_connect (pDocView, "style-updated", G_CALLBACK(onStyleContextChanged), nullptr);
2514 static void lok_doc_view_set_property (GObject* object, guint propId, const GValue *value, GParamSpec *pspec)
2516 LOKDocView* pDocView = LOK_DOC_VIEW (object);
2517 LOKDocViewPrivate& priv = getPrivate(pDocView);
2518 bool bDocPasswordEnabled = priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD;
2519 bool bDocPasswordToModifyEnabled = priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY;
2520 bool bTiledAnnotationsEnabled = !(priv->m_nLOKFeatures & LOK_FEATURE_NO_TILED_ANNOTATIONS);
2522 switch (propId)
2524 case PROP_LO_PATH:
2525 priv->m_aLOPath = g_value_get_string (value);
2526 break;
2527 case PROP_LO_UNIPOLL:
2528 priv->m_bUnipoll = g_value_get_boolean (value);
2529 break;
2530 case PROP_LO_POINTER:
2531 priv->m_pOffice = static_cast<LibreOfficeKit*>(g_value_get_pointer(value));
2532 break;
2533 case PROP_USER_PROFILE_URL:
2534 if (const gchar* pUserProfile = g_value_get_string(value))
2535 priv->m_aUserProfileURL = pUserProfile;
2536 break;
2537 case PROP_DOC_PATH:
2538 priv->m_aDocPath = g_value_get_string (value);
2539 break;
2540 case PROP_DOC_POINTER:
2541 priv->m_pDocument = static_cast<LibreOfficeKitDocument*>(g_value_get_pointer(value));
2542 priv->m_eDocumentType = static_cast<LibreOfficeKitDocumentType>(priv->m_pDocument->pClass->getDocumentType(priv->m_pDocument));
2543 break;
2544 case PROP_EDITABLE:
2545 lok_doc_view_set_edit (pDocView, g_value_get_boolean (value));
2546 break;
2547 case PROP_ZOOM:
2548 lok_doc_view_set_zoom (pDocView, g_value_get_float (value));
2549 break;
2550 case PROP_DOC_WIDTH:
2551 priv->m_nDocumentWidthTwips = g_value_get_long (value);
2552 break;
2553 case PROP_DOC_HEIGHT:
2554 priv->m_nDocumentHeightTwips = g_value_get_long (value);
2555 break;
2556 case PROP_DOC_PASSWORD:
2557 if (bool(g_value_get_boolean (value)) != bDocPasswordEnabled)
2559 priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_DOCUMENT_PASSWORD;
2560 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2562 break;
2563 case PROP_DOC_PASSWORD_TO_MODIFY:
2564 if ( bool(g_value_get_boolean (value)) != bDocPasswordToModifyEnabled)
2566 priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY;
2567 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2569 break;
2570 case PROP_TILED_ANNOTATIONS:
2571 if ( bool(g_value_get_boolean (value)) != bTiledAnnotationsEnabled)
2573 priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_NO_TILED_ANNOTATIONS;
2574 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2576 break;
2577 default:
2578 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec);
2582 static void lok_doc_view_get_property (GObject* object, guint propId, GValue *value, GParamSpec *pspec)
2584 LOKDocView* pDocView = LOK_DOC_VIEW (object);
2585 LOKDocViewPrivate& priv = getPrivate(pDocView);
2587 switch (propId)
2589 case PROP_LO_PATH:
2590 g_value_set_string (value, priv->m_aLOPath.c_str());
2591 break;
2592 case PROP_LO_UNIPOLL:
2593 g_value_set_boolean (value, priv->m_bUnipoll);
2594 break;
2595 case PROP_LO_POINTER:
2596 g_value_set_pointer(value, priv->m_pOffice);
2597 break;
2598 case PROP_USER_PROFILE_URL:
2599 g_value_set_string(value, priv->m_aUserProfileURL.c_str());
2600 break;
2601 case PROP_DOC_PATH:
2602 g_value_set_string (value, priv->m_aDocPath.c_str());
2603 break;
2604 case PROP_DOC_POINTER:
2605 g_value_set_pointer(value, priv->m_pDocument);
2606 break;
2607 case PROP_EDITABLE:
2608 g_value_set_boolean (value, priv->m_bEdit);
2609 break;
2610 case PROP_LOAD_PROGRESS:
2611 g_value_set_double (value, priv->m_nLoadProgress);
2612 break;
2613 case PROP_ZOOM:
2614 g_value_set_float (value, priv->m_fZoom);
2615 break;
2616 case PROP_IS_LOADING:
2617 g_value_set_boolean (value, priv->m_bIsLoading);
2618 break;
2619 case PROP_IS_INITIALIZED:
2620 g_value_set_boolean (value, priv->m_bInit);
2621 break;
2622 case PROP_DOC_WIDTH:
2623 g_value_set_long (value, priv->m_nDocumentWidthTwips);
2624 break;
2625 case PROP_DOC_HEIGHT:
2626 g_value_set_long (value, priv->m_nDocumentHeightTwips);
2627 break;
2628 case PROP_CAN_ZOOM_IN:
2629 g_value_set_boolean (value, priv->m_bCanZoomIn);
2630 break;
2631 case PROP_CAN_ZOOM_OUT:
2632 g_value_set_boolean (value, priv->m_bCanZoomOut);
2633 break;
2634 case PROP_DOC_PASSWORD:
2635 g_value_set_boolean (value, (priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD) != 0);
2636 break;
2637 case PROP_DOC_PASSWORD_TO_MODIFY:
2638 g_value_set_boolean (value, (priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY) != 0);
2639 break;
2640 case PROP_TILED_ANNOTATIONS:
2641 g_value_set_boolean (value, !(priv->m_nLOKFeatures & LOK_FEATURE_NO_TILED_ANNOTATIONS));
2642 break;
2643 default:
2644 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec);
2648 static gboolean lok_doc_view_draw (GtkWidget* pWidget, cairo_t* pCairo)
2650 LOKDocView *pDocView = LOK_DOC_VIEW (pWidget);
2652 renderDocument (pDocView, pCairo);
2653 renderOverlay (pDocView, pCairo);
2655 return FALSE;
2658 //rhbz#1444437 finalize may not occur immediately when this widget is destroyed
2659 //it may happen during GC of javascript, e.g. in gnome-documents but "destroy"
2660 //will be called promptly, so close documents in destroy, not finalize
2661 static void lok_doc_view_destroy (GtkWidget* widget)
2663 LOKDocView* pDocView = LOK_DOC_VIEW (widget);
2664 LOKDocViewPrivate& priv = getPrivate(pDocView);
2666 // Ignore notifications sent to this view on shutdown.
2667 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2668 std::stringstream ss;
2669 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2670 g_info("%s", ss.str().c_str());
2671 if (priv->m_pDocument)
2673 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2674 priv->m_pDocument->pClass->registerCallback(priv->m_pDocument, nullptr, nullptr);
2677 if (priv->lokThreadPool)
2679 g_thread_pool_free(priv->lokThreadPool, true, true);
2680 priv->lokThreadPool = nullptr;
2683 aGuard.unlock();
2685 if (priv->m_pDocument)
2687 if (priv->m_pDocument->pClass->getViewsCount(priv->m_pDocument) > 1)
2689 priv->m_pDocument->pClass->destroyView(priv->m_pDocument, priv->m_nViewId);
2691 else
2693 if (priv->m_pDocument)
2695 priv->m_pDocument->pClass->destroy (priv->m_pDocument);
2696 priv->m_pDocument = nullptr;
2698 if (priv->m_pOffice)
2700 priv->m_pOffice->pClass->destroy (priv->m_pOffice);
2701 priv->m_pOffice = nullptr;
2706 GTK_WIDGET_CLASS (lok_doc_view_parent_class)->destroy (widget);
2709 static void lok_doc_view_finalize (GObject* object)
2711 LOKDocView* pDocView = LOK_DOC_VIEW (object);
2712 LOKDocViewPrivate& priv = getPrivate(pDocView);
2714 delete priv.m_pImpl;
2715 priv.m_pImpl = nullptr;
2717 G_OBJECT_CLASS (lok_doc_view_parent_class)->finalize (object);
2720 // kicks the mainloop awake
2721 static gboolean timeout_wakeup(void *)
2723 return FALSE;
2726 // integrate our mainloop with LOK's
2727 static int lok_poll_callback(void*, int timeoutUs)
2729 if (timeoutUs)
2731 guint timeout = g_timeout_add(timeoutUs / 1000, timeout_wakeup, nullptr);
2732 g_main_context_iteration(nullptr, true);
2733 g_source_remove(timeout);
2735 else
2736 g_main_context_iteration(nullptr, FALSE);
2738 return 0;
2741 // thread-safe wakeup of our mainloop
2742 static void lok_wake_callback(void *)
2744 g_main_context_wakeup(nullptr);
2747 static gboolean spin_lok_loop(void *pData)
2749 LOKDocView *pDocView = LOK_DOC_VIEW (pData);
2750 LOKDocViewPrivate& priv = getPrivate(pDocView);
2751 priv->m_pOffice->pClass->runLoop(priv->m_pOffice, lok_poll_callback, lok_wake_callback, nullptr);
2752 return FALSE;
2755 // Update the client's view size
2756 static void updateClientZoom(LOKDocView *pDocView)
2758 LOKDocViewPrivate& priv = getPrivate(pDocView);
2759 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
2760 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
2761 GError* error = nullptr;
2763 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2764 LOEvent* pLOEvent = new LOEvent(LOK_SET_CLIENT_ZOOM);
2765 pLOEvent->m_nTilePixelWidth = nTileSizePixelsScaled;
2766 pLOEvent->m_nTilePixelHeight = nTileSizePixelsScaled;
2767 pLOEvent->m_nTileTwipWidth = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom * nScaleFactor);
2768 pLOEvent->m_nTileTwipHeight = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom * nScaleFactor);
2769 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2771 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2772 if (error != nullptr)
2774 g_warning("Unable to call LOK_SET_CLIENT_ZOOM: %s", error->message);
2775 g_clear_error(&error);
2777 g_object_unref(task);
2779 priv->m_nTileSizeTwips = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom * nScaleFactor);
2782 static gboolean lok_doc_view_initable_init (GInitable *initable, GCancellable* /*cancellable*/, GError **error)
2784 LOKDocView *pDocView = LOK_DOC_VIEW (initable);
2785 LOKDocViewPrivate& priv = getPrivate(pDocView);
2787 if (priv->m_pOffice != nullptr)
2788 return true;
2790 if (priv->m_bUnipoll)
2791 (void)g_setenv("SAL_LOK_OPTIONS", "unipoll", FALSE);
2793 static const char testingLangs[] = "de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru";
2794 (void)g_setenv("LOK_ALLOWLIST_LANGUAGES", testingLangs, FALSE);
2796 priv->m_pOffice = lok_init_2(priv->m_aLOPath.c_str(), priv->m_aUserProfileURL.empty() ? nullptr : priv->m_aUserProfileURL.c_str());
2798 if (priv->m_pOffice == nullptr)
2800 g_set_error (error,
2801 g_quark_from_static_string ("LOK initialization error"), 0,
2802 "Failed to get LibreOfficeKit context. Make sure path (%s) is correct",
2803 priv->m_aLOPath.c_str());
2804 return FALSE;
2806 priv->m_nLOKFeatures |= LOK_FEATURE_PART_IN_INVALIDATION_CALLBACK;
2807 priv->m_nLOKFeatures |= LOK_FEATURE_VIEWID_IN_VISCURSOR_INVALIDATION_CALLBACK;
2808 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2810 if (priv->m_bUnipoll)
2811 g_idle_add(spin_lok_loop, pDocView);
2813 return true;
2816 static void lok_doc_view_initable_iface_init (GInitableIface *iface)
2818 iface->init = lok_doc_view_initable_init;
2821 static void lok_doc_view_class_init (LOKDocViewClass* pClass)
2823 GObjectClass *pGObjectClass = G_OBJECT_CLASS(pClass);
2824 GtkWidgetClass *pWidgetClass = GTK_WIDGET_CLASS(pClass);
2826 pGObjectClass->get_property = lok_doc_view_get_property;
2827 pGObjectClass->set_property = lok_doc_view_set_property;
2828 pGObjectClass->finalize = lok_doc_view_finalize;
2830 pWidgetClass->draw = lok_doc_view_draw;
2831 pWidgetClass->button_press_event = lok_doc_view_signal_button;
2832 pWidgetClass->button_release_event = lok_doc_view_signal_button;
2833 pWidgetClass->key_press_event = signalKey;
2834 pWidgetClass->key_release_event = signalKey;
2835 pWidgetClass->motion_notify_event = lok_doc_view_signal_motion;
2836 pWidgetClass->destroy = lok_doc_view_destroy;
2839 * LOKDocView:lopath:
2841 * The absolute path of the LibreOffice install.
2843 properties[PROP_LO_PATH] =
2844 g_param_spec_string("lopath",
2845 "LO Path",
2846 "LibreOffice Install Path",
2847 nullptr,
2848 static_cast<GParamFlags>(G_PARAM_READWRITE |
2849 G_PARAM_CONSTRUCT_ONLY |
2850 G_PARAM_STATIC_STRINGS));
2853 * LOKDocView:unipoll:
2855 * Whether we use our own unified polling mainloop in place of glib's
2857 properties[PROP_LO_UNIPOLL] =
2858 g_param_spec_boolean("unipoll",
2859 "Unified Polling",
2860 "Whether we use a custom unified polling loop",
2861 FALSE,
2862 static_cast<GParamFlags>(G_PARAM_READWRITE |
2863 G_PARAM_CONSTRUCT_ONLY |
2864 G_PARAM_STATIC_STRINGS));
2866 * LOKDocView:lopointer:
2868 * A LibreOfficeKit* in case lok_init() is already called
2869 * previously.
2871 properties[PROP_LO_POINTER] =
2872 g_param_spec_pointer("lopointer",
2873 "LO Pointer",
2874 "A LibreOfficeKit* from lok_init()",
2875 static_cast<GParamFlags>(G_PARAM_READWRITE |
2876 G_PARAM_CONSTRUCT_ONLY |
2877 G_PARAM_STATIC_STRINGS));
2880 * LOKDocView:userprofileurl:
2882 * The absolute path of the LibreOffice user profile.
2884 properties[PROP_USER_PROFILE_URL] =
2885 g_param_spec_string("userprofileurl",
2886 "User profile path",
2887 "LibreOffice user profile path",
2888 nullptr,
2889 static_cast<GParamFlags>(G_PARAM_READWRITE |
2890 G_PARAM_CONSTRUCT_ONLY |
2891 G_PARAM_STATIC_STRINGS));
2894 * LOKDocView:docpath:
2896 * The path of the document that is currently being viewed.
2898 properties[PROP_DOC_PATH] =
2899 g_param_spec_string("docpath",
2900 "Document Path",
2901 "The URI of the document to open",
2902 nullptr,
2903 static_cast<GParamFlags>(G_PARAM_READWRITE |
2904 G_PARAM_STATIC_STRINGS));
2907 * LOKDocView:docpointer:
2909 * A LibreOfficeKitDocument* in case documentLoad() is already called
2910 * previously.
2912 properties[PROP_DOC_POINTER] =
2913 g_param_spec_pointer("docpointer",
2914 "Document Pointer",
2915 "A LibreOfficeKitDocument* from documentLoad()",
2916 static_cast<GParamFlags>(G_PARAM_READWRITE |
2917 G_PARAM_STATIC_STRINGS));
2920 * LOKDocView:editable:
2922 * Whether the document loaded inside of #LOKDocView is editable or not.
2924 properties[PROP_EDITABLE] =
2925 g_param_spec_boolean("editable",
2926 "Editable",
2927 "Whether the content is in edit mode or not",
2928 FALSE,
2929 static_cast<GParamFlags>(G_PARAM_READWRITE |
2930 G_PARAM_STATIC_STRINGS));
2933 * LOKDocView:load-progress:
2935 * The percent completion of the current loading operation of the
2936 * document. This can be used for progress bars. Note that this is not a
2937 * very accurate progress indicator, and its value might reset it couple of
2938 * times to 0 and start again. You should not rely on its numbers.
2940 properties[PROP_LOAD_PROGRESS] =
2941 g_param_spec_double("load-progress",
2942 "Estimated Load Progress",
2943 "Shows the progress of the document load operation",
2944 0.0, 1.0, 0.0,
2945 static_cast<GParamFlags>(G_PARAM_READABLE |
2946 G_PARAM_STATIC_STRINGS));
2949 * LOKDocView:zoom-level:
2951 * The current zoom level of the document loaded inside #LOKDocView. The
2952 * default value is 1.0.
2954 properties[PROP_ZOOM] =
2955 g_param_spec_float("zoom-level",
2956 "Zoom Level",
2957 "The current zoom level of the content",
2958 0, 5.0, 1.0,
2959 static_cast<GParamFlags>(G_PARAM_READWRITE |
2960 G_PARAM_STATIC_STRINGS));
2963 * LOKDocView:is-loading:
2965 * Whether the requested document is being loaded or not. %TRUE if it is
2966 * being loaded, otherwise %FALSE.
2968 properties[PROP_IS_LOADING] =
2969 g_param_spec_boolean("is-loading",
2970 "Is Loading",
2971 "Whether the view is loading a document",
2972 FALSE,
2973 static_cast<GParamFlags>(G_PARAM_READABLE |
2974 G_PARAM_STATIC_STRINGS));
2977 * LOKDocView:is-initialized:
2979 * Whether the requested document has completely loaded or not.
2981 properties[PROP_IS_INITIALIZED] =
2982 g_param_spec_boolean("is-initialized",
2983 "Has initialized",
2984 "Whether the view has completely initialized",
2985 FALSE,
2986 static_cast<GParamFlags>(G_PARAM_READABLE |
2987 G_PARAM_STATIC_STRINGS));
2990 * LOKDocView:doc-width:
2992 * The width of the currently loaded document in #LOKDocView in twips.
2994 properties[PROP_DOC_WIDTH] =
2995 g_param_spec_long("doc-width",
2996 "Document Width",
2997 "Width of the document in twips",
2998 0, G_MAXLONG, 0,
2999 static_cast<GParamFlags>(G_PARAM_READWRITE |
3000 G_PARAM_STATIC_STRINGS));
3003 * LOKDocView:doc-height:
3005 * The height of the currently loaded document in #LOKDocView in twips.
3007 properties[PROP_DOC_HEIGHT] =
3008 g_param_spec_long("doc-height",
3009 "Document Height",
3010 "Height of the document in twips",
3011 0, G_MAXLONG, 0,
3012 static_cast<GParamFlags>(G_PARAM_READWRITE |
3013 G_PARAM_STATIC_STRINGS));
3016 * LOKDocView:can-zoom-in:
3018 * It tells whether the view can further be zoomed in or not.
3020 properties[PROP_CAN_ZOOM_IN] =
3021 g_param_spec_boolean("can-zoom-in",
3022 "Can Zoom In",
3023 "Whether the view can be zoomed in further",
3024 true,
3025 static_cast<GParamFlags>(G_PARAM_READABLE
3026 | G_PARAM_STATIC_STRINGS));
3029 * LOKDocView:can-zoom-out:
3031 * It tells whether the view can further be zoomed out or not.
3033 properties[PROP_CAN_ZOOM_OUT] =
3034 g_param_spec_boolean("can-zoom-out",
3035 "Can Zoom Out",
3036 "Whether the view can be zoomed out further",
3037 true,
3038 static_cast<GParamFlags>(G_PARAM_READABLE
3039 | G_PARAM_STATIC_STRINGS));
3042 * LOKDocView:doc-password:
3044 * Set it to true if client supports providing password for viewing
3045 * password protected documents
3047 properties[PROP_DOC_PASSWORD] =
3048 g_param_spec_boolean("doc-password",
3049 "Document password capability",
3050 "Whether client supports providing document passwords",
3051 FALSE,
3052 static_cast<GParamFlags>(G_PARAM_READWRITE
3053 | G_PARAM_STATIC_STRINGS));
3056 * LOKDocView:doc-password-to-modify:
3058 * Set it to true if client supports providing password for edit-protected documents
3060 properties[PROP_DOC_PASSWORD_TO_MODIFY] =
3061 g_param_spec_boolean("doc-password-to-modify",
3062 "Edit document password capability",
3063 "Whether the client supports providing passwords to edit documents",
3064 FALSE,
3065 static_cast<GParamFlags>(G_PARAM_READWRITE
3066 | G_PARAM_STATIC_STRINGS));
3069 * LOKDocView:tiled-annotations-rendering:
3071 * Set it to false if client does not want LO to render comments in tiles and
3072 * instead interested in using comments API to access comments
3074 properties[PROP_TILED_ANNOTATIONS] =
3075 g_param_spec_boolean("tiled-annotations",
3076 "Render comments in tiles",
3077 "Whether the client wants in tile comment rendering",
3078 true,
3079 static_cast<GParamFlags>(G_PARAM_READWRITE
3080 | G_PARAM_STATIC_STRINGS));
3082 g_object_class_install_properties(pGObjectClass, PROP_LAST, properties);
3085 * LOKDocView::load-changed:
3086 * @pDocView: the #LOKDocView on which the signal is emitted
3087 * @fLoadProgress: the new progress value
3089 doc_view_signals[LOAD_CHANGED] =
3090 g_signal_new("load-changed",
3091 G_TYPE_FROM_CLASS (pGObjectClass),
3092 G_SIGNAL_RUN_FIRST,
3094 nullptr, nullptr,
3095 g_cclosure_marshal_VOID__DOUBLE,
3096 G_TYPE_NONE, 1,
3097 G_TYPE_DOUBLE);
3100 * LOKDocView::edit-changed:
3101 * @pDocView: the #LOKDocView on which the signal is emitted
3102 * @bEdit: the new edit value of the view
3104 doc_view_signals[EDIT_CHANGED] =
3105 g_signal_new("edit-changed",
3106 G_TYPE_FROM_CLASS (pGObjectClass),
3107 G_SIGNAL_RUN_FIRST,
3109 nullptr, nullptr,
3110 g_cclosure_marshal_VOID__BOOLEAN,
3111 G_TYPE_NONE, 1,
3112 G_TYPE_BOOLEAN);
3115 * LOKDocView::command-changed:
3116 * @pDocView: the #LOKDocView on which the signal is emitted
3117 * @aCommand: the command that was changed
3119 doc_view_signals[COMMAND_CHANGED] =
3120 g_signal_new("command-changed",
3121 G_TYPE_FROM_CLASS(pGObjectClass),
3122 G_SIGNAL_RUN_FIRST,
3124 nullptr, nullptr,
3125 g_cclosure_marshal_VOID__STRING,
3126 G_TYPE_NONE, 1,
3127 G_TYPE_STRING);
3130 * LOKDocView::search-not-found:
3131 * @pDocView: the #LOKDocView on which the signal is emitted
3132 * @aCommand: the string for which the search was not found.
3134 doc_view_signals[SEARCH_NOT_FOUND] =
3135 g_signal_new("search-not-found",
3136 G_TYPE_FROM_CLASS(pGObjectClass),
3137 G_SIGNAL_RUN_FIRST,
3139 nullptr, nullptr,
3140 g_cclosure_marshal_VOID__STRING,
3141 G_TYPE_NONE, 1,
3142 G_TYPE_STRING);
3145 * LOKDocView::part-changed:
3146 * @pDocView: the #LOKDocView on which the signal is emitted
3147 * @aCommand: the part number which the view changed to
3149 doc_view_signals[PART_CHANGED] =
3150 g_signal_new("part-changed",
3151 G_TYPE_FROM_CLASS(pGObjectClass),
3152 G_SIGNAL_RUN_FIRST,
3154 nullptr, nullptr,
3155 g_cclosure_marshal_VOID__INT,
3156 G_TYPE_NONE, 1,
3157 G_TYPE_INT);
3160 * LOKDocView::size-changed:
3161 * @pDocView: the #LOKDocView on which the signal is emitted
3162 * @aCommand: NULL, we just notify that want to notify the UI elements that are interested.
3164 doc_view_signals[SIZE_CHANGED] =
3165 g_signal_new("size-changed",
3166 G_TYPE_FROM_CLASS(pGObjectClass),
3167 G_SIGNAL_RUN_FIRST,
3169 nullptr, nullptr,
3170 g_cclosure_marshal_VOID__VOID,
3171 G_TYPE_NONE, 1,
3172 G_TYPE_INT);
3175 * LOKDocView::hyperlinked-clicked:
3176 * @pDocView: the #LOKDocView on which the signal is emitted
3177 * @aHyperlink: the URI which the application should handle
3179 doc_view_signals[HYPERLINK_CLICKED] =
3180 g_signal_new("hyperlink-clicked",
3181 G_TYPE_FROM_CLASS(pGObjectClass),
3182 G_SIGNAL_RUN_FIRST,
3184 nullptr, nullptr,
3185 g_cclosure_marshal_VOID__STRING,
3186 G_TYPE_NONE, 1,
3187 G_TYPE_STRING);
3190 * LOKDocView::cursor-changed:
3191 * @pDocView: the #LOKDocView on which the signal is emitted
3192 * @nX: The new cursor position (X coordinate) in pixels
3193 * @nY: The new cursor position (Y coordinate) in pixels
3194 * @nWidth: The width of new cursor
3195 * @nHeight: The height of new cursor
3197 doc_view_signals[CURSOR_CHANGED] =
3198 g_signal_new("cursor-changed",
3199 G_TYPE_FROM_CLASS(pGObjectClass),
3200 G_SIGNAL_RUN_FIRST,
3202 nullptr, nullptr,
3203 g_cclosure_marshal_generic,
3204 G_TYPE_NONE, 4,
3205 G_TYPE_INT, G_TYPE_INT,
3206 G_TYPE_INT, G_TYPE_INT);
3209 * LOKDocView::search-result-count:
3210 * @pDocView: the #LOKDocView on which the signal is emitted
3211 * @aCommand: number of matches.
3213 doc_view_signals[SEARCH_RESULT_COUNT] =
3214 g_signal_new("search-result-count",
3215 G_TYPE_FROM_CLASS(pGObjectClass),
3216 G_SIGNAL_RUN_FIRST,
3218 nullptr, nullptr,
3219 g_cclosure_marshal_VOID__STRING,
3220 G_TYPE_NONE, 1,
3221 G_TYPE_STRING);
3224 * LOKDocView::command-result:
3225 * @pDocView: the #LOKDocView on which the signal is emitted
3226 * @aCommand: JSON containing the info about the command that finished,
3227 * and its success status.
3229 doc_view_signals[COMMAND_RESULT] =
3230 g_signal_new("command-result",
3231 G_TYPE_FROM_CLASS(pGObjectClass),
3232 G_SIGNAL_RUN_FIRST,
3234 nullptr, nullptr,
3235 g_cclosure_marshal_VOID__STRING,
3236 G_TYPE_NONE, 1,
3237 G_TYPE_STRING);
3240 * LOKDocView::address-changed:
3241 * @pDocView: the #LOKDocView on which the signal is emitted
3242 * @aCommand: formula text content
3244 doc_view_signals[ADDRESS_CHANGED] =
3245 g_signal_new("address-changed",
3246 G_TYPE_FROM_CLASS(pGObjectClass),
3247 G_SIGNAL_RUN_FIRST,
3249 nullptr, nullptr,
3250 g_cclosure_marshal_VOID__STRING,
3251 G_TYPE_NONE, 1,
3252 G_TYPE_STRING);
3255 * LOKDocView::formula-changed:
3256 * @pDocView: the #LOKDocView on which the signal is emitted
3257 * @aCommand: formula text content
3259 doc_view_signals[FORMULA_CHANGED] =
3260 g_signal_new("formula-changed",
3261 G_TYPE_FROM_CLASS(pGObjectClass),
3262 G_SIGNAL_RUN_FIRST,
3264 nullptr, nullptr,
3265 g_cclosure_marshal_VOID__STRING,
3266 G_TYPE_NONE, 1,
3267 G_TYPE_STRING);
3270 * LOKDocView::text-selection:
3271 * @pDocView: the #LOKDocView on which the signal is emitted
3272 * @bIsTextSelected: whether text selected is non-null
3274 doc_view_signals[TEXT_SELECTION] =
3275 g_signal_new("text-selection",
3276 G_TYPE_FROM_CLASS(pGObjectClass),
3277 G_SIGNAL_RUN_FIRST,
3279 nullptr, nullptr,
3280 g_cclosure_marshal_VOID__BOOLEAN,
3281 G_TYPE_NONE, 1,
3282 G_TYPE_BOOLEAN);
3285 * LOKDocView::password-required:
3286 * @pDocView: the #LOKDocView on which the signal is emitted
3287 * @pUrl: URL of the document for which password is required
3288 * @bModify: whether password id required to modify the document
3289 * This is true when password is required to edit the document,
3290 * while it can still be viewed without password. In such cases, provide a NULL
3291 * password for read-only access to the document.
3292 * If false, password is required for opening the document, and document
3293 * cannot be opened without providing a valid password.
3295 * Password must be provided by calling lok_doc_view_set_document_password
3296 * function with pUrl as provided by the callback.
3298 * Upon entering an invalid password, another `password-required` signal is
3299 * emitted.
3300 * Upon entering a valid password, document starts to load.
3301 * Upon entering a NULL password: if bModify is %TRUE, document starts to
3302 * open in view-only mode, else loading of document is aborted.
3304 doc_view_signals[PASSWORD_REQUIRED] =
3305 g_signal_new("password-required",
3306 G_TYPE_FROM_CLASS(pGObjectClass),
3307 G_SIGNAL_RUN_FIRST,
3309 nullptr, nullptr,
3310 g_cclosure_marshal_generic,
3311 G_TYPE_NONE, 2,
3312 G_TYPE_STRING,
3313 G_TYPE_BOOLEAN);
3316 * LOKDocView::comment:
3317 * @pDocView: the #LOKDocView on which the signal is emitted
3318 * @pComment: the JSON string containing comment notification
3319 * The has following structure containing the information telling whether
3320 * the comment has been added, deleted or modified.
3321 * The example:
3323 * "comment": {
3324 * "action": "Add",
3325 * "id": "11",
3326 * "parent": "4",
3327 * "author": "Unknown Author",
3328 * "text": "This is a comment",
3329 * "dateTime": "2016-08-18T13:13:00",
3330 * "anchorPos": "4529, 3906",
3331 * "textRange": "1418, 3906, 3111, 919"
3334 * 'action' can be 'Add', 'Remove' or 'Modify' depending on whether
3335 * comment has been added, removed or modified.
3336 * 'parent' is a non-zero comment id if this comment is a reply comment,
3337 * otherwise it's a root comment.
3339 doc_view_signals[COMMENT] =
3340 g_signal_new("comment",
3341 G_TYPE_FROM_CLASS(pGObjectClass),
3342 G_SIGNAL_RUN_FIRST,
3344 nullptr, nullptr,
3345 g_cclosure_marshal_generic,
3346 G_TYPE_NONE, 1,
3347 G_TYPE_STRING);
3350 * LOKDocView::ruler:
3351 * @pDocView: the #LOKDocView on which the signal is emitted
3352 * @pPayload: the JSON string containing the information about ruler properties
3354 * The payload format is:
3357 * "margin1": "...",
3358 * "margin2": "...",
3359 * "leftOffset": "...",
3360 * "pageOffset": "...",
3361 * "pageWidth": "...",
3362 * "unit": "..."
3365 doc_view_signals[RULER] =
3366 g_signal_new("ruler",
3367 G_TYPE_FROM_CLASS(pGObjectClass),
3368 G_SIGNAL_RUN_FIRST,
3370 nullptr, nullptr,
3371 g_cclosure_marshal_generic,
3372 G_TYPE_NONE, 1,
3373 G_TYPE_STRING);
3376 * LOKDocView::window::
3377 * @pDocView: the #LOKDocView on which the signal is emitted
3378 * @pPayload: the JSON string containing the information about the window
3380 * This signal emits information about external windows like dialogs, autopopups for now.
3382 * The payload format of pPayload is:
3385 * "id": "unique integer id of the dialog",
3386 * "action": "<see below>",
3387 * "type": "<see below>"
3388 * "rectangle": "x, y, width, height"
3391 * "type" tells the type of the window the action is associated with
3392 * - "dialog" - window is a dialog
3393 * - "child" - window is a floating window (combo boxes, etc.)
3395 * "action" can take following values:
3396 * - "created" - window is created in the backend, client can render it now
3397 * - "title_changed" - window's title is changed
3398 * - "size_changed" - window's size is changed
3399 * - "invalidate" - the area as described by "rectangle" is invalidated
3400 * Clients must request the new area
3401 * - "cursor_invalidate" - cursor is invalidated. New position is in "rectangle"
3402 * - "cursor_visible" - cursor visible status is changed. Status is available
3403 * in "visible" field
3404 * - "close" - window is closed
3406 doc_view_signals[WINDOW] =
3407 g_signal_new("window",
3408 G_TYPE_FROM_CLASS(pGObjectClass),
3409 G_SIGNAL_RUN_FIRST,
3411 nullptr, nullptr,
3412 g_cclosure_marshal_generic,
3413 G_TYPE_NONE, 1,
3414 G_TYPE_STRING);
3417 * LOKDocView::invalidate-header::
3418 * @pDocView: the #LOKDocView on which the signal is emitted
3419 * @pPayload: can be either "row", "column", or "all".
3421 * The column/row header is no more valid because of a column/row insertion
3422 * or a similar event. Clients must query a new column/row header set.
3424 * The payload says if we are invalidating a row or column header
3426 doc_view_signals[INVALIDATE_HEADER] =
3427 g_signal_new("invalidate-header",
3428 G_TYPE_FROM_CLASS(pGObjectClass),
3429 G_SIGNAL_RUN_FIRST,
3431 nullptr, nullptr,
3432 g_cclosure_marshal_generic,
3433 G_TYPE_NONE, 1,
3434 G_TYPE_STRING);
3437 SAL_DLLPUBLIC_EXPORT GtkWidget*
3438 lok_doc_view_new (const gchar* pPath, GCancellable *cancellable, GError **error)
3440 return GTK_WIDGET (g_initable_new (LOK_TYPE_DOC_VIEW, cancellable, error,
3441 "lopath", pPath == nullptr ? LOK_PATH : pPath,
3442 "halign", GTK_ALIGN_CENTER,
3443 "valign", GTK_ALIGN_CENTER,
3444 nullptr));
3447 SAL_DLLPUBLIC_EXPORT GtkWidget*
3448 lok_doc_view_new_from_user_profile (const gchar* pPath, const gchar* pUserProfile, GCancellable *cancellable, GError **error)
3450 return GTK_WIDGET(g_initable_new(LOK_TYPE_DOC_VIEW, cancellable, error,
3451 "lopath", pPath == nullptr ? LOK_PATH : pPath,
3452 "userprofileurl", pUserProfile,
3453 "halign", GTK_ALIGN_CENTER,
3454 "valign", GTK_ALIGN_CENTER,
3455 nullptr));
3458 SAL_DLLPUBLIC_EXPORT GtkWidget* lok_doc_view_new_from_widget(LOKDocView* pOldLOKDocView,
3459 const gchar* pRenderingArguments)
3461 LOKDocViewPrivate& pOldPriv = getPrivate(pOldLOKDocView);
3462 GtkWidget* pNewDocView = GTK_WIDGET(g_initable_new(LOK_TYPE_DOC_VIEW, /*cancellable=*/nullptr, /*error=*/nullptr,
3463 "lopath", pOldPriv->m_aLOPath.c_str(),
3464 "userprofileurl", pOldPriv->m_aUserProfileURL.c_str(),
3465 "lopointer", pOldPriv->m_pOffice,
3466 "docpointer", pOldPriv->m_pDocument,
3467 "halign", GTK_ALIGN_CENTER,
3468 "valign", GTK_ALIGN_CENTER,
3469 nullptr));
3471 // No documentLoad(), just a createView().
3472 LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(pNewDocView));
3473 LOKDocViewPrivate& pNewPriv = getPrivate(LOK_DOC_VIEW(pNewDocView));
3474 // Store the view id only later in postDocumentLoad(), as
3475 // initializeForRendering() changes the id in Impress.
3476 pDocument->pClass->createView(pDocument);
3477 pNewPriv->m_aRenderingArguments = pRenderingArguments;
3479 postDocumentLoad(pNewDocView);
3480 return pNewDocView;
3483 SAL_DLLPUBLIC_EXPORT gboolean
3484 lok_doc_view_open_document_finish (LOKDocView* pDocView, GAsyncResult* res, GError** error)
3486 GTask* task = G_TASK(res);
3488 g_return_val_if_fail(g_task_is_valid(res, pDocView), false);
3489 g_return_val_if_fail(g_task_get_source_tag(task) == lok_doc_view_open_document, false);
3490 g_return_val_if_fail(error == nullptr || *error == nullptr, false);
3492 return g_task_propagate_boolean(task, error);
3495 SAL_DLLPUBLIC_EXPORT void
3496 lok_doc_view_open_document (LOKDocView* pDocView,
3497 const gchar* pPath,
3498 const gchar* pRenderingArguments,
3499 GCancellable* cancellable,
3500 GAsyncReadyCallback callback,
3501 gpointer userdata)
3503 GTask* task = g_task_new(pDocView, cancellable, callback, userdata);
3504 LOKDocViewPrivate& priv = getPrivate(pDocView);
3505 GError* error = nullptr;
3507 LOEvent* pLOEvent = new LOEvent(LOK_LOAD_DOC);
3509 g_object_set(G_OBJECT(pDocView), "docpath", pPath, nullptr);
3510 if (pRenderingArguments)
3511 priv->m_aRenderingArguments = pRenderingArguments;
3512 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3513 g_task_set_source_tag(task, reinterpret_cast<gpointer>(lok_doc_view_open_document));
3515 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3516 if (error != nullptr)
3518 g_warning("Unable to call LOK_LOAD_DOC: %s", error->message);
3519 g_clear_error(&error);
3521 g_object_unref(task);
3524 SAL_DLLPUBLIC_EXPORT LibreOfficeKitDocument*
3525 lok_doc_view_get_document (LOKDocView* pDocView)
3527 LOKDocViewPrivate& priv = getPrivate(pDocView);
3528 return priv->m_pDocument;
3531 SAL_DLLPUBLIC_EXPORT void
3532 lok_doc_view_set_visible_area (LOKDocView* pDocView, GdkRectangle* pVisibleArea)
3534 if (!pVisibleArea)
3535 return;
3537 LOKDocViewPrivate& priv = getPrivate(pDocView);
3538 priv->m_aVisibleArea = *pVisibleArea;
3539 priv->m_bVisibleAreaSet = true;
3542 namespace {
3543 // This used to be rtl::math::approxEqual() but since that isn't inline anymore
3544 // in rtl/math.hxx and was moved into libuno_sal as rtl_math_approxEqual() to
3545 // cater for representable integer cases and we don't want to link against
3546 // libuno_sal, we'll have to have an own implementation. The special large
3547 // integer cases seems not be needed here.
3548 bool lok_approxEqual(double a, double b)
3550 static const double e48 = 1.0 / (16777216.0 * 16777216.0);
3551 if (a == b)
3552 return true;
3553 if (a == 0.0 || b == 0.0)
3554 return false;
3555 const double d = fabs(a - b);
3556 return (d < fabs(a) * e48 && d < fabs(b) * e48);
3560 SAL_DLLPUBLIC_EXPORT void
3561 lok_doc_view_set_zoom (LOKDocView* pDocView, float fZoom)
3563 LOKDocViewPrivate& priv = getPrivate(pDocView);
3565 if (!priv->m_pDocument)
3566 return;
3568 // Clamp the input value in [MIN_ZOOM, MAX_ZOOM]
3569 fZoom = fZoom < MIN_ZOOM ? MIN_ZOOM : fZoom;
3570 fZoom = std::min(fZoom, MAX_ZOOM);
3572 if (lok_approxEqual(fZoom, priv->m_fZoom))
3573 return;
3575 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
3576 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
3577 priv->m_fZoom = fZoom;
3578 long nDocumentWidthPixels = twipToPixel(priv->m_nDocumentWidthTwips, fZoom * nScaleFactor);
3579 long nDocumentHeightPixels = twipToPixel(priv->m_nDocumentHeightTwips, fZoom * nScaleFactor);
3580 // Total number of columns in this document.
3581 guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixelsScaled);
3582 priv->m_pTileBuffer = std::make_unique<TileBuffer>(nColumns, nScaleFactor);
3583 gtk_widget_set_size_request(GTK_WIDGET(pDocView),
3584 nDocumentWidthPixels / nScaleFactor,
3585 nDocumentHeightPixels / nScaleFactor);
3587 g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_ZOOM]);
3589 // set properties to indicate if view can be further zoomed in/out
3590 bool bCanZoomIn = priv->m_fZoom < MAX_ZOOM;
3591 bool bCanZoomOut = priv->m_fZoom > MIN_ZOOM;
3592 if (bCanZoomIn != bool(priv->m_bCanZoomIn))
3594 priv->m_bCanZoomIn = bCanZoomIn;
3595 g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_CAN_ZOOM_IN]);
3597 if (bCanZoomOut != bool(priv->m_bCanZoomOut))
3599 priv->m_bCanZoomOut = bCanZoomOut;
3600 g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_CAN_ZOOM_OUT]);
3603 updateClientZoom(pDocView);
3606 SAL_DLLPUBLIC_EXPORT gfloat
3607 lok_doc_view_get_zoom (LOKDocView* pDocView)
3609 LOKDocViewPrivate& priv = getPrivate(pDocView);
3610 return priv->m_fZoom;
3613 SAL_DLLPUBLIC_EXPORT gint
3614 lok_doc_view_get_parts (LOKDocView* pDocView)
3616 LOKDocViewPrivate& priv = getPrivate(pDocView);
3617 if (!priv->m_pDocument)
3618 return -1;
3620 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
3621 std::stringstream ss;
3622 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
3623 g_info("%s", ss.str().c_str());
3624 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
3625 return priv->m_pDocument->pClass->getParts( priv->m_pDocument );
3628 SAL_DLLPUBLIC_EXPORT gint
3629 lok_doc_view_get_part (LOKDocView* pDocView)
3631 LOKDocViewPrivate& priv = getPrivate(pDocView);
3632 if (!priv->m_pDocument)
3633 return -1;
3635 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
3636 std::stringstream ss;
3637 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
3638 g_info("%s", ss.str().c_str());
3639 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
3640 return priv->m_pDocument->pClass->getPart( priv->m_pDocument );
3643 SAL_DLLPUBLIC_EXPORT void
3644 lok_doc_view_set_part (LOKDocView* pDocView, int nPart)
3646 LOKDocViewPrivate& priv = getPrivate(pDocView);
3647 if (!priv->m_pDocument)
3648 return;
3650 if (nPart < 0 || nPart >= priv->m_nParts)
3652 g_warning("Invalid part request : %d", nPart);
3653 return;
3656 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3657 LOEvent* pLOEvent = new LOEvent(LOK_SET_PART);
3658 GError* error = nullptr;
3660 pLOEvent->m_nPart = nPart;
3661 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3663 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3664 if (error != nullptr)
3666 g_warning("Unable to call LOK_SET_PART: %s", error->message);
3667 g_clear_error(&error);
3669 g_object_unref(task);
3670 priv->m_nPartId = nPart;
3673 SAL_DLLPUBLIC_EXPORT gchar*
3674 lok_doc_view_get_part_name (LOKDocView* pDocView, int nPart)
3676 LOKDocViewPrivate& priv = getPrivate(pDocView);
3677 if (!priv->m_pDocument)
3678 return nullptr;
3680 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
3681 std::stringstream ss;
3682 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
3683 g_info("%s", ss.str().c_str());
3684 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
3685 return priv->m_pDocument->pClass->getPartName( priv->m_pDocument, nPart );
3688 SAL_DLLPUBLIC_EXPORT void
3689 lok_doc_view_set_partmode(LOKDocView* pDocView,
3690 int nPartMode)
3692 LOKDocViewPrivate& priv = getPrivate(pDocView);
3693 if (!priv->m_pDocument)
3694 return;
3696 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3697 LOEvent* pLOEvent = new LOEvent(LOK_SET_PARTMODE);
3698 GError* error = nullptr;
3700 pLOEvent->m_nPartMode = nPartMode;
3701 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3703 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3704 if (error != nullptr)
3706 g_warning("Unable to call LOK_SET_PARTMODE: %s", error->message);
3707 g_clear_error(&error);
3709 g_object_unref(task);
3712 SAL_DLLPUBLIC_EXPORT void
3713 lok_doc_view_reset_view(LOKDocView* pDocView)
3715 LOKDocViewPrivate& priv = getPrivate(pDocView);
3717 if (priv->m_pTileBuffer != nullptr)
3718 priv->m_pTileBuffer->resetAllTiles();
3719 priv->m_nLoadProgress = 0.0;
3721 memset(&priv->m_aVisibleCursor, 0, sizeof(priv->m_aVisibleCursor));
3722 priv->m_bCursorOverlayVisible = false;
3723 priv->m_bCursorVisible = false;
3725 priv->m_nLastButtonPressTime = 0;
3726 priv->m_nLastButtonReleaseTime = 0;
3727 priv->m_aTextSelectionRectangles.clear();
3729 memset(&priv->m_aTextSelectionStart, 0, sizeof(priv->m_aTextSelectionStart));
3730 memset(&priv->m_aTextSelectionEnd, 0, sizeof(priv->m_aTextSelectionEnd));
3731 memset(&priv->m_aGraphicSelection, 0, sizeof(priv->m_aGraphicSelection));
3732 priv->m_bInDragGraphicSelection = false;
3733 memset(&priv->m_aCellCursor, 0, sizeof(priv->m_aCellCursor));
3735 cairo_surface_destroy(priv->m_pHandleStart);
3736 priv->m_pHandleStart = nullptr;
3737 memset(&priv->m_aHandleStartRect, 0, sizeof(priv->m_aHandleStartRect));
3738 priv->m_bInDragStartHandle = false;
3740 cairo_surface_destroy(priv->m_pHandleMiddle);
3741 priv->m_pHandleMiddle = nullptr;
3742 memset(&priv->m_aHandleMiddleRect, 0, sizeof(priv->m_aHandleMiddleRect));
3743 priv->m_bInDragMiddleHandle = false;
3745 cairo_surface_destroy(priv->m_pHandleEnd);
3746 priv->m_pHandleEnd = nullptr;
3747 memset(&priv->m_aHandleEndRect, 0, sizeof(priv->m_aHandleEndRect));
3748 priv->m_bInDragEndHandle = false;
3750 memset(&priv->m_aGraphicHandleRects, 0, sizeof(priv->m_aGraphicHandleRects));
3751 memset(&priv->m_bInDragGraphicHandles, 0, sizeof(priv->m_bInDragGraphicHandles));
3753 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
3756 SAL_DLLPUBLIC_EXPORT void
3757 lok_doc_view_set_edit(LOKDocView* pDocView,
3758 gboolean bEdit)
3760 LOKDocViewPrivate& priv = getPrivate(pDocView);
3761 if (!priv->m_pDocument)
3762 return;
3764 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3765 LOEvent* pLOEvent = new LOEvent(LOK_SET_EDIT);
3766 GError* error = nullptr;
3768 pLOEvent->m_bEdit = bEdit;
3769 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3771 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3772 if (error != nullptr)
3774 g_warning("Unable to call LOK_SET_EDIT: %s", error->message);
3775 g_clear_error(&error);
3777 g_object_unref(task);
3780 SAL_DLLPUBLIC_EXPORT gboolean
3781 lok_doc_view_get_edit (LOKDocView* pDocView)
3783 LOKDocViewPrivate& priv = getPrivate(pDocView);
3784 return priv->m_bEdit;
3787 SAL_DLLPUBLIC_EXPORT void
3788 lok_doc_view_post_command (LOKDocView* pDocView,
3789 const gchar* pCommand,
3790 const gchar* pArguments,
3791 gboolean bNotifyWhenFinished)
3793 LOKDocViewPrivate& priv = getPrivate(pDocView);
3794 if (!priv->m_pDocument)
3795 return;
3797 if (priv->m_bEdit)
3798 LOKPostCommand(pDocView, pCommand, pArguments, bNotifyWhenFinished);
3799 else
3800 g_info ("LOK_POST_COMMAND: ignoring commands in view-only mode");
3803 SAL_DLLPUBLIC_EXPORT gchar *
3804 lok_doc_view_get_command_values (LOKDocView* pDocView,
3805 const gchar* pCommand)
3807 g_return_val_if_fail (LOK_IS_DOC_VIEW (pDocView), nullptr);
3808 g_return_val_if_fail (pCommand != nullptr, nullptr);
3810 LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(pDocView);
3811 if (!pDocument)
3812 return nullptr;
3814 return pDocument->pClass->getCommandValues(pDocument, pCommand);
3817 SAL_DLLPUBLIC_EXPORT void
3818 lok_doc_view_find_prev (LOKDocView* pDocView,
3819 const gchar* pText,
3820 gboolean bHighlightAll)
3822 doSearch(pDocView, pText, true, bHighlightAll);
3825 SAL_DLLPUBLIC_EXPORT void
3826 lok_doc_view_find_next (LOKDocView* pDocView,
3827 const gchar* pText,
3828 gboolean bHighlightAll)
3830 doSearch(pDocView, pText, false, bHighlightAll);
3833 SAL_DLLPUBLIC_EXPORT void
3834 lok_doc_view_highlight_all (LOKDocView* pDocView,
3835 const gchar* pText)
3837 doSearch(pDocView, pText, false, true);
3840 SAL_DLLPUBLIC_EXPORT gchar*
3841 lok_doc_view_copy_selection (LOKDocView* pDocView,
3842 const gchar* pMimeType,
3843 gchar** pUsedMimeType)
3845 LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(pDocView);
3846 if (!pDocument)
3847 return nullptr;
3849 std::stringstream ss;
3850 ss << "lok::Document::getTextSelection('" << pMimeType << "')";
3851 g_info("%s", ss.str().c_str());
3852 return pDocument->pClass->getTextSelection(pDocument, pMimeType, pUsedMimeType);
3855 SAL_DLLPUBLIC_EXPORT gboolean
3856 lok_doc_view_paste (LOKDocView* pDocView,
3857 const gchar* pMimeType,
3858 const gchar* pData,
3859 gsize nSize)
3861 LOKDocViewPrivate& priv = getPrivate(pDocView);
3862 LibreOfficeKitDocument* pDocument = priv->m_pDocument;
3863 bool ret = false;
3865 if (!pDocument)
3866 return false;
3868 if (!priv->m_bEdit)
3870 g_info ("ignoring paste in view-only mode");
3871 return ret;
3874 if (pData)
3876 std::stringstream ss;
3877 ss << "lok::Document::paste('" << pMimeType << "', '" << std::string(pData, nSize) << ", "<<nSize<<"')";
3878 g_info("%s", ss.str().c_str());
3879 ret = pDocument->pClass->paste(pDocument, pMimeType, pData, nSize);
3882 return ret;
3885 SAL_DLLPUBLIC_EXPORT void
3886 lok_doc_view_set_document_password (LOKDocView* pDocView,
3887 const gchar* pURL,
3888 const gchar* pPassword)
3890 LOKDocViewPrivate& priv = getPrivate(pDocView);
3892 priv->m_pOffice->pClass->setDocumentPassword(priv->m_pOffice, pURL, pPassword);
3895 SAL_DLLPUBLIC_EXPORT gchar*
3896 lok_doc_view_get_version_info (LOKDocView* pDocView)
3898 LOKDocViewPrivate& priv = getPrivate(pDocView);
3900 return priv->m_pOffice->pClass->getVersionInfo(priv->m_pOffice);
3904 SAL_DLLPUBLIC_EXPORT gfloat
3905 lok_doc_view_pixel_to_twip (LOKDocView* pDocView, float fInput)
3907 LOKDocViewPrivate& priv = getPrivate(pDocView);
3908 return pixelToTwip(fInput, priv->m_fZoom);
3911 SAL_DLLPUBLIC_EXPORT gfloat
3912 lok_doc_view_twip_to_pixel (LOKDocView* pDocView, float fInput)
3914 LOKDocViewPrivate& priv = getPrivate(pDocView);
3915 return twipToPixel(fInput, priv->m_fZoom);
3918 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */