lokdocview: Add missing callback cases
[LibreOffice.git] / libreofficekit / source / gtk / lokdocview.cxx
blob4fa07b9c60c1fb17f1e20267656863f70646c9d4
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <iostream>
18 #include <mutex>
19 #include <boost/property_tree/json_parser.hpp>
21 #include <com/sun/star/awt/Key.hpp>
22 #include <LibreOfficeKit/LibreOfficeKit.h>
23 #include <LibreOfficeKit/LibreOfficeKitInit.h>
24 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
25 #include <LibreOfficeKit/LibreOfficeKitGtk.h>
26 #include <vcl/event.hxx>
28 #include "tilebuffer.hxx"
30 #if !GLIB_CHECK_VERSION(2,32,0)
31 #define G_SOURCE_REMOVE FALSE
32 #define G_SOURCE_CONTINUE TRUE
33 #endif
34 #if !GLIB_CHECK_VERSION(2,40,0)
35 #define g_info(...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__)
36 #endif
38 // Cursor bitmaps from the installation set.
39 #define CURSOR_HANDLE_DIR "/../share/libreofficekit/"
40 // Number of handles around a graphic selection.
41 #define GRAPHIC_HANDLE_COUNT 8
42 // Maximum Zoom allowed
43 #define MAX_ZOOM 5.0f
44 // Minimum Zoom allowed
45 #define MIN_ZOOM 0.25f
47 /// This is expected to be locked during setView(), doSomethingElse() LOK calls.
48 static std::mutex g_aLOKMutex;
50 /// Same as a GdkRectangle, but also tracks in which part the rectangle is.
51 struct ViewRectangle
53 int m_nPart;
54 GdkRectangle m_aRectangle;
56 ViewRectangle(int nPart = 0, const GdkRectangle& rRectangle = GdkRectangle())
57 : m_nPart(nPart),
58 m_aRectangle(rRectangle)
63 /// Same as a list of GdkRectangles, but also tracks in which part the rectangle is.
64 struct ViewRectangles
66 int m_nPart;
67 std::vector<GdkRectangle> m_aRectangles;
69 ViewRectangles(int nPart = 0, const std::vector<GdkRectangle>& rRectangles = std::vector<GdkRectangle>())
70 : m_nPart(nPart),
71 m_aRectangles(rRectangles)
76 /// Private struct used by this GObject type
77 struct LOKDocViewPrivateImpl
79 std::string m_aLOPath;
80 std::string m_aUserProfileURL;
81 std::string m_aDocPath;
82 std::string m_aRenderingArguments;
83 gdouble m_nLoadProgress;
84 gboolean m_bIsLoading;
85 gboolean m_bInit; // initializeForRendering() has been called
86 gboolean m_bCanZoomIn;
87 gboolean m_bCanZoomOut;
88 gboolean m_bUnipoll;
89 LibreOfficeKit* m_pOffice;
90 LibreOfficeKitDocument* m_pDocument;
92 std::unique_ptr<TileBuffer> m_pTileBuffer;
93 GThreadPool* lokThreadPool;
95 gfloat m_fZoom;
96 glong m_nDocumentWidthTwips;
97 glong m_nDocumentHeightTwips;
98 /// View or edit mode.
99 gboolean m_bEdit;
100 /// LOK Features
101 guint64 m_nLOKFeatures;
102 /// Number of parts in currently loaded document
103 gint m_nParts;
104 /// Position and size of the visible cursor.
105 GdkRectangle m_aVisibleCursor;
106 /// Position and size of the view cursors. The current view can only see
107 /// them, can't modify them. Key is the view id.
108 std::map<int, ViewRectangle> m_aViewCursors;
109 /// Cursor overlay is visible or hidden (for blinking).
110 gboolean m_bCursorOverlayVisible;
111 /// Cursor is visible or hidden (e.g. for graphic selection).
112 gboolean m_bCursorVisible;
113 /// Visibility of view selections. The current view can only see / them,
114 /// can't modify them. Key is the view id.
115 std::map<int, bool> m_aViewCursorVisibilities;
116 /// Time of the last button press.
117 guint32 m_nLastButtonPressTime;
118 /// Time of the last button release.
119 guint32 m_nLastButtonReleaseTime;
120 /// Last pressed button (left, right, middle)
121 guint32 m_nLastButtonPressed;
122 /// Key modifier (ctrl, atl, shift)
123 guint32 m_nKeyModifier;
124 /// Rectangles of the current text selection.
125 std::vector<GdkRectangle> m_aTextSelectionRectangles;
126 /// Rectangles of view selections. The current view can only see
127 /// them, can't modify them. Key is the view id.
128 std::map<int, ViewRectangles> m_aTextViewSelectionRectangles;
129 /// Position and size of the selection start (as if there would be a cursor caret there).
130 GdkRectangle m_aTextSelectionStart;
131 /// Position and size of the selection end.
132 GdkRectangle m_aTextSelectionEnd;
133 GdkRectangle m_aGraphicSelection;
134 /// Position and size of the graphic view selections. The current view can only
135 /// see them, can't modify them. Key is the view id.
136 std::map<int, ViewRectangle> m_aGraphicViewSelections;
137 GdkRectangle m_aCellCursor;
138 /// Position and size of the cell view cursors. The current view can only
139 /// see them, can't modify them. Key is the view id.
140 std::map<int, ViewRectangle> m_aCellViewCursors;
141 gboolean m_bInDragGraphicSelection;
143 /// @name Start/middle/end handle.
144 ///@{
145 /// Bitmap of the text selection start handle.
146 cairo_surface_t* m_pHandleStart;
147 /// Rectangle of the text selection start handle, to know if the user clicked on it or not
148 GdkRectangle m_aHandleStartRect;
149 /// If we are in the middle of a drag of the text selection end handle.
150 gboolean m_bInDragStartHandle;
151 /// Bitmap of the text selection middle handle.
152 cairo_surface_t* m_pHandleMiddle;
153 /// Rectangle of the text selection middle handle, to know if the user clicked on it or not
154 GdkRectangle m_aHandleMiddleRect;
155 /// If we are in the middle of a drag of the text selection middle handle.
156 gboolean m_bInDragMiddleHandle;
157 /// Bitmap of the text selection end handle.
158 cairo_surface_t* m_pHandleEnd;
159 /// Rectangle of the text selection end handle, to know if the user clicked on it or not
160 GdkRectangle m_aHandleEndRect;
161 /// If we are in the middle of a drag of the text selection end handle.
162 gboolean m_bInDragEndHandle;
163 ///@}
165 /// @name Graphic handles.
166 ///@{
167 /// Rectangle of a graphic selection handle, to know if the user clicked on it or not.
168 GdkRectangle m_aGraphicHandleRects[8];
169 /// If we are in the middle of a drag of a graphic selection handle.
170 gboolean m_bInDragGraphicHandles[8];
171 ///@}
173 /// View ID, returned by createView() or 0 by default.
174 int m_nViewId;
176 /// Cached part ID, returned by getPart().
177 int m_nPartId;
179 /// Cached document type, returned by getDocumentType().
180 LibreOfficeKitDocumentType m_eDocumentType;
182 /// Contains a freshly set zoom level: logic size of a tile.
183 /// It gets reset back to 0 when LOK was informed about this zoom change.
184 int m_nTileSizeTwips;
186 GdkRectangle m_aVisibleArea;
187 bool m_bVisibleAreaSet;
189 /// Event source ID for handleTimeout() of this widget.
190 guint m_nTimeoutId;
192 /// Rectangles of view locks. The current view can only see
193 /// them, can't modify them. Key is the view id.
194 std::map<int, ViewRectangle> m_aViewLockRectangles;
196 LOKDocViewPrivateImpl()
197 : m_nLoadProgress(0),
198 m_bIsLoading(false),
199 m_bInit(false),
200 m_bCanZoomIn(true),
201 m_bCanZoomOut(true),
202 m_bUnipoll(false),
203 m_pOffice(nullptr),
204 m_pDocument(nullptr),
205 lokThreadPool(nullptr),
206 m_fZoom(0),
207 m_nDocumentWidthTwips(0),
208 m_nDocumentHeightTwips(0),
209 m_bEdit(FALSE),
210 m_nLOKFeatures(0),
211 m_nParts(0),
212 m_aVisibleCursor({0, 0, 0, 0}),
213 m_bCursorOverlayVisible(false),
214 m_bCursorVisible(true),
215 m_nLastButtonPressTime(0),
216 m_nLastButtonReleaseTime(0),
217 m_nLastButtonPressed(0),
218 m_nKeyModifier(0),
219 m_aTextSelectionStart({0, 0, 0, 0}),
220 m_aTextSelectionEnd({0, 0, 0, 0}),
221 m_aGraphicSelection({0, 0, 0, 0}),
222 m_aCellCursor({0, 0, 0, 0}),
223 m_bInDragGraphicSelection(false),
224 m_pHandleStart(nullptr),
225 m_aHandleStartRect({0, 0, 0, 0}),
226 m_bInDragStartHandle(0),
227 m_pHandleMiddle(nullptr),
228 m_aHandleMiddleRect({0, 0, 0, 0}),
229 m_bInDragMiddleHandle(false),
230 m_pHandleEnd(nullptr),
231 m_aHandleEndRect({0, 0, 0, 0}),
232 m_bInDragEndHandle(false),
233 m_nViewId(0),
234 m_nPartId(0),
235 m_eDocumentType(LOK_DOCTYPE_OTHER),
236 m_nTileSizeTwips(0),
237 m_aVisibleArea({0, 0, 0, 0}),
238 m_bVisibleAreaSet(false),
239 m_nTimeoutId(0)
241 memset(&m_aGraphicHandleRects, 0, sizeof(m_aGraphicHandleRects));
242 memset(&m_bInDragGraphicHandles, 0, sizeof(m_bInDragGraphicHandles));
245 ~LOKDocViewPrivateImpl()
247 if (m_nTimeoutId)
248 g_source_remove(m_nTimeoutId);
252 /// Wrapper around LOKDocViewPrivateImpl, managed by malloc/memset/free.
253 struct _LOKDocViewPrivate
255 LOKDocViewPrivateImpl* m_pImpl;
257 LOKDocViewPrivateImpl* operator->()
259 return m_pImpl;
263 enum
265 LOAD_CHANGED,
266 EDIT_CHANGED,
267 COMMAND_CHANGED,
268 SEARCH_NOT_FOUND,
269 PART_CHANGED,
270 SIZE_CHANGED,
271 HYPERLINK_CLICKED,
272 CURSOR_CHANGED,
273 SEARCH_RESULT_COUNT,
274 COMMAND_RESULT,
275 ADDRESS_CHANGED,
276 FORMULA_CHANGED,
277 TEXT_SELECTION,
278 PASSWORD_REQUIRED,
279 COMMENT,
280 RULER,
281 WINDOW,
282 INVALIDATE_HEADER,
284 LAST_SIGNAL
287 enum
289 PROP_0,
291 PROP_LO_PATH,
292 PROP_LO_UNIPOLL,
293 PROP_LO_POINTER,
294 PROP_USER_PROFILE_URL,
295 PROP_DOC_PATH,
296 PROP_DOC_POINTER,
297 PROP_EDITABLE,
298 PROP_LOAD_PROGRESS,
299 PROP_ZOOM,
300 PROP_IS_LOADING,
301 PROP_IS_INITIALIZED,
302 PROP_DOC_WIDTH,
303 PROP_DOC_HEIGHT,
304 PROP_CAN_ZOOM_IN,
305 PROP_CAN_ZOOM_OUT,
306 PROP_DOC_PASSWORD,
307 PROP_DOC_PASSWORD_TO_MODIFY,
308 PROP_TILED_ANNOTATIONS,
310 PROP_LAST
313 static guint doc_view_signals[LAST_SIGNAL] = { 0 };
314 static GParamSpec *properties[PROP_LAST] = { nullptr };
316 static void lok_doc_view_initable_iface_init (GInitableIface *iface);
317 static void callbackWorker (int nType, const char* pPayload, void* pData);
319 SAL_DLLPUBLIC_EXPORT GType lok_doc_view_get_type();
320 #ifdef __GNUC__
321 #pragma GCC diagnostic push
322 #pragma GCC diagnostic ignored "-Wunused-function"
323 #endif
324 G_DEFINE_TYPE_WITH_CODE (LOKDocView, lok_doc_view, GTK_TYPE_DRAWING_AREA,
325 G_ADD_PRIVATE (LOKDocView)
326 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, lok_doc_view_initable_iface_init));
327 #ifdef __GNUC__
328 #pragma GCC diagnostic pop
329 #endif
331 static LOKDocViewPrivate& getPrivate(LOKDocView* pDocView)
333 LOKDocViewPrivate* priv = static_cast<LOKDocViewPrivate*>(lok_doc_view_get_instance_private(pDocView));
334 return *priv;
337 /// Helper struct used to pass the data from soffice thread -> main thread.
338 struct CallbackData
340 int m_nType;
341 std::string m_aPayload;
342 LOKDocView* m_pDocView;
344 CallbackData(int nType, const std::string& rPayload, LOKDocView* pDocView)
345 : m_nType(nType),
346 m_aPayload(rPayload),
347 m_pDocView(pDocView) {}
350 static void
351 payloadToSize(const char* pPayload, long& rWidth, long& rHeight)
353 rWidth = rHeight = 0;
354 gchar** ppCoordinates = g_strsplit(pPayload, ", ", 2);
355 gchar** ppCoordinate = ppCoordinates;
356 if (!*ppCoordinate)
357 return;
358 rWidth = atoi(*ppCoordinate);
359 ++ppCoordinate;
360 if (!*ppCoordinate)
361 return;
362 rHeight = atoi(*ppCoordinate);
363 g_strfreev(ppCoordinates);
366 static void
367 LOKPostCommand (LOKDocView* pDocView,
368 const gchar* pCommand,
369 const gchar* pArguments,
370 gboolean 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));
592 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
593 std::stringstream ss;
594 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
595 g_info("%s", ss.str().c_str());
596 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
598 if (priv->m_nTileSizeTwips)
600 ss.str(std::string());
601 ss << "lok::Document::setClientZoom(" << nTileSizePixels << ", " << nTileSizePixels << ", " << priv->m_nTileSizeTwips << ", " << priv->m_nTileSizeTwips << ")";
602 g_info("%s", ss.str().c_str());
603 priv->m_pDocument->pClass->setClientZoom(priv->m_pDocument,
604 nTileSizePixels,
605 nTileSizePixels,
606 priv->m_nTileSizeTwips,
607 priv->m_nTileSizeTwips);
608 priv->m_nTileSizeTwips = 0;
610 if (priv->m_bVisibleAreaSet)
612 ss.str(std::string());
613 ss << "lok::Document::setClientVisibleArea(" << priv->m_aVisibleArea.x << ", " << priv->m_aVisibleArea.y << ", ";
614 ss << priv->m_aVisibleArea.width << ", " << priv->m_aVisibleArea.height << ")";
615 g_info("%s", ss.str().c_str());
616 priv->m_pDocument->pClass->setClientVisibleArea(priv->m_pDocument,
617 priv->m_aVisibleArea.x,
618 priv->m_aVisibleArea.y,
619 priv->m_aVisibleArea.width,
620 priv->m_aVisibleArea.height);
621 priv->m_bVisibleAreaSet = false;
624 ss.str(std::string());
625 ss << "lok::Document::postKeyEvent(" << pLOEvent->m_nKeyEvent << ", " << pLOEvent->m_nCharCode << ", " << pLOEvent->m_nKeyCode << ")";
626 g_info("%s", ss.str().c_str());
627 priv->m_pDocument->pClass->postKeyEvent(priv->m_pDocument,
628 pLOEvent->m_nKeyEvent,
629 pLOEvent->m_nCharCode,
630 pLOEvent->m_nKeyCode);
633 static gboolean
634 signalKey (GtkWidget* pWidget, GdkEventKey* pEvent)
636 LOKDocView* pDocView = LOK_DOC_VIEW(pWidget);
637 LOKDocViewPrivate& priv = getPrivate(pDocView);
638 int nCharCode = 0;
639 int nKeyCode = 0;
640 GError* error = nullptr;
642 if (!priv->m_bEdit)
644 g_info("signalKey: not in edit mode, ignore");
645 return FALSE;
648 priv->m_nKeyModifier &= KEY_MOD2;
649 switch (pEvent->keyval)
651 case GDK_KEY_BackSpace:
652 nKeyCode = com::sun::star::awt::Key::BACKSPACE;
653 break;
654 case GDK_KEY_Delete:
655 nKeyCode = com::sun::star::awt::Key::DELETE;
656 break;
657 case GDK_KEY_Return:
658 case GDK_KEY_KP_Enter:
659 nKeyCode = com::sun::star::awt::Key::RETURN;
660 break;
661 case GDK_KEY_Escape:
662 nKeyCode = com::sun::star::awt::Key::ESCAPE;
663 break;
664 case GDK_KEY_Tab:
665 nKeyCode = com::sun::star::awt::Key::TAB;
666 break;
667 case GDK_KEY_Down:
668 nKeyCode = com::sun::star::awt::Key::DOWN;
669 break;
670 case GDK_KEY_Up:
671 nKeyCode = com::sun::star::awt::Key::UP;
672 break;
673 case GDK_KEY_Left:
674 nKeyCode = com::sun::star::awt::Key::LEFT;
675 break;
676 case GDK_KEY_Right:
677 nKeyCode = com::sun::star::awt::Key::RIGHT;
678 break;
679 case GDK_KEY_Page_Down:
680 nKeyCode = com::sun::star::awt::Key::PAGEDOWN;
681 break;
682 case GDK_KEY_Page_Up:
683 nKeyCode = com::sun::star::awt::Key::PAGEUP;
684 break;
685 case GDK_KEY_Insert:
686 nKeyCode = com::sun::star::awt::Key::INSERT;
687 break;
688 case GDK_KEY_Shift_L:
689 case GDK_KEY_Shift_R:
690 if (pEvent->type == GDK_KEY_PRESS)
691 priv->m_nKeyModifier |= KEY_SHIFT;
692 break;
693 case GDK_KEY_Control_L:
694 case GDK_KEY_Control_R:
695 if (pEvent->type == GDK_KEY_PRESS)
696 priv->m_nKeyModifier |= KEY_MOD1;
697 break;
698 case GDK_KEY_Alt_L:
699 case GDK_KEY_Alt_R:
700 if (pEvent->type == GDK_KEY_PRESS)
701 priv->m_nKeyModifier |= KEY_MOD2;
702 else
703 priv->m_nKeyModifier &= ~KEY_MOD2;
704 break;
705 default:
706 if (pEvent->keyval >= GDK_KEY_F1 && pEvent->keyval <= GDK_KEY_F26)
707 nKeyCode = com::sun::star::awt::Key::F1 + (pEvent->keyval - GDK_KEY_F1);
708 else
709 nCharCode = gdk_keyval_to_unicode(pEvent->keyval);
712 // rsc is not public API, but should be good enough for debugging purposes.
713 // If this is needed for real, then probably a new param of type
714 // css::awt::KeyModifier is needed in postKeyEvent().
715 if (pEvent->state & GDK_SHIFT_MASK)
716 nKeyCode |= KEY_SHIFT;
718 if (pEvent->state & GDK_CONTROL_MASK)
719 nKeyCode |= KEY_MOD1;
721 if (priv->m_nKeyModifier & KEY_MOD2)
722 nKeyCode |= KEY_MOD2;
724 if (nKeyCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2)) {
725 if (pEvent->keyval >= GDK_KEY_a && pEvent->keyval <= GDK_KEY_z)
727 nKeyCode |= 512 + (pEvent->keyval - GDK_KEY_a);
729 else if (pEvent->keyval >= GDK_KEY_A && pEvent->keyval <= GDK_KEY_Z) {
730 nKeyCode |= 512 + (pEvent->keyval - GDK_KEY_A);
732 else if (pEvent->keyval >= GDK_KEY_0 && pEvent->keyval <= GDK_KEY_9) {
733 nKeyCode |= 256 + (pEvent->keyval - GDK_KEY_0);
737 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
738 LOEvent* pLOEvent = new LOEvent(LOK_POST_KEY);
739 pLOEvent->m_nKeyEvent = pEvent->type == GDK_KEY_RELEASE ? LOK_KEYEVENT_KEYUP : LOK_KEYEVENT_KEYINPUT;
740 pLOEvent->m_nCharCode = nCharCode;
741 pLOEvent->m_nKeyCode = nKeyCode;
742 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
743 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
744 if (error != nullptr)
746 g_warning("Unable to call LOK_POST_KEY: %s", error->message);
747 g_clear_error(&error);
749 g_object_unref(task);
751 return FALSE;
754 static gboolean
755 handleTimeout (gpointer pData)
757 LOKDocView* pDocView = LOK_DOC_VIEW (pData);
758 LOKDocViewPrivate& priv = getPrivate(pDocView);
760 if (priv->m_bEdit)
762 if (priv->m_bCursorOverlayVisible)
763 priv->m_bCursorOverlayVisible = false;
764 else
765 priv->m_bCursorOverlayVisible = true;
766 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
769 return G_SOURCE_CONTINUE;
772 static void
773 commandChanged(LOKDocView* pDocView, const std::string& rString)
775 g_signal_emit(pDocView, doc_view_signals[COMMAND_CHANGED], 0, rString.c_str());
778 static void
779 searchNotFound(LOKDocView* pDocView, const std::string& rString)
781 g_signal_emit(pDocView, doc_view_signals[SEARCH_NOT_FOUND], 0, rString.c_str());
784 static void searchResultCount(LOKDocView* pDocView, const std::string& rString)
786 g_signal_emit(pDocView, doc_view_signals[SEARCH_RESULT_COUNT], 0, rString.c_str());
789 static void commandResult(LOKDocView* pDocView, const std::string& rString)
791 g_signal_emit(pDocView, doc_view_signals[COMMAND_RESULT], 0, rString.c_str());
794 static void addressChanged(LOKDocView* pDocView, const std::string& rString)
796 g_signal_emit(pDocView, doc_view_signals[ADDRESS_CHANGED], 0, rString.c_str());
799 static void formulaChanged(LOKDocView* pDocView, const std::string& rString)
801 g_signal_emit(pDocView, doc_view_signals[FORMULA_CHANGED], 0, rString.c_str());
804 static void reportError(LOKDocView* /*pDocView*/, const std::string& rString)
806 GtkWidget *dialog = gtk_message_dialog_new(nullptr,
807 GTK_DIALOG_DESTROY_WITH_PARENT,
808 GTK_MESSAGE_ERROR,
809 GTK_BUTTONS_CLOSE,
810 "%s",
811 rString.c_str());
812 gtk_dialog_run(GTK_DIALOG(dialog));
813 gtk_widget_destroy(dialog);
816 static void
817 setPart(LOKDocView* pDocView, const std::string& rString)
819 LOKDocViewPrivate& priv = getPrivate(pDocView);
820 priv->m_nPartId = std::stoi(rString);
821 g_signal_emit(pDocView, doc_view_signals[PART_CHANGED], 0, priv->m_nPartId);
824 static void
825 hyperlinkClicked(LOKDocView* pDocView, const std::string& rString)
827 g_signal_emit(pDocView, doc_view_signals[HYPERLINK_CLICKED], 0, rString.c_str());
830 /// Trigger a redraw, invoked on the main thread by other functions running in a thread.
831 static gboolean queueDraw(gpointer pData)
833 GtkWidget* pWidget = static_cast<GtkWidget*>(pData);
835 gtk_widget_queue_draw(pWidget);
837 return G_SOURCE_REMOVE;
840 /// Looks up the author string from initializeForRendering()'s rendering arguments.
841 static std::string getAuthorRenderingArgument(LOKDocViewPrivate& priv)
843 std::stringstream aStream;
844 aStream << priv->m_aRenderingArguments;
845 boost::property_tree::ptree aTree;
846 boost::property_tree::read_json(aStream, aTree);
847 std::string aRet;
848 for (const std::pair<std::string, boost::property_tree::ptree>& rPair : aTree)
850 if (rPair.first == ".uno:Author")
852 aRet = rPair.second.get<std::string>("value");
853 break;
856 return aRet;
859 /// Author string <-> View ID map
860 static std::map<std::string, int> g_aAuthorViews;
862 /// Set up LOKDocView after the document is loaded, invoked on the main thread by openDocumentInThread() running in a thread.
863 static gboolean postDocumentLoad(gpointer pData)
865 LOKDocView* pLOKDocView = static_cast<LOKDocView*>(pData);
866 LOKDocViewPrivate& priv = getPrivate(pLOKDocView);
868 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
869 priv->m_pDocument->pClass->initializeForRendering(priv->m_pDocument, priv->m_aRenderingArguments.c_str());
870 priv->m_nViewId = priv->m_pDocument->pClass->getView(priv->m_pDocument);
871 g_aAuthorViews[getAuthorRenderingArgument(priv)] = priv->m_nViewId;
872 priv->m_pDocument->pClass->registerCallback(priv->m_pDocument, callbackWorker, pLOKDocView);
873 priv->m_pDocument->pClass->getDocumentSize(priv->m_pDocument, &priv->m_nDocumentWidthTwips, &priv->m_nDocumentHeightTwips);
874 priv->m_nParts = priv->m_pDocument->pClass->getParts(priv->m_pDocument);
875 aGuard.unlock();
876 priv->m_nTimeoutId = g_timeout_add(600, handleTimeout, pLOKDocView);
878 float zoom = priv->m_fZoom;
879 long nDocumentWidthTwips = priv->m_nDocumentWidthTwips;
880 long nDocumentHeightTwips = priv->m_nDocumentHeightTwips;
881 long nDocumentWidthPixels = twipToPixel(nDocumentWidthTwips, zoom);
882 long nDocumentHeightPixels = twipToPixel(nDocumentHeightTwips, zoom);
883 // Total number of columns in this document.
884 guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixels);
886 priv->m_pTileBuffer = std::make_unique<TileBuffer>(nColumns);
887 gtk_widget_set_size_request(GTK_WIDGET(pLOKDocView),
888 nDocumentWidthPixels,
889 nDocumentHeightPixels);
890 gtk_widget_set_can_focus(GTK_WIDGET(pLOKDocView), TRUE);
891 gtk_widget_grab_focus(GTK_WIDGET(pLOKDocView));
892 lok_doc_view_set_zoom(pLOKDocView, 1.0);
894 // we are completely loaded
895 priv->m_bInit = TRUE;
897 return G_SOURCE_REMOVE;
900 /// Implementation of the global callback handler, invoked by globalCallback();
901 static gboolean
902 globalCallback (gpointer pData)
904 CallbackData* pCallback = static_cast<CallbackData*>(pData);
905 LOKDocViewPrivate& priv = getPrivate(pCallback->m_pDocView);
906 gboolean bModify = false;
908 switch (pCallback->m_nType)
910 case LOK_CALLBACK_STATUS_INDICATOR_START:
912 priv->m_nLoadProgress = 0.0;
913 g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, 0.0);
915 break;
916 case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
918 priv->m_nLoadProgress = static_cast<gdouble>(std::stoi(pCallback->m_aPayload)/100.0);
919 g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, priv->m_nLoadProgress);
921 break;
922 case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
924 priv->m_nLoadProgress = 1.0;
925 g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, 1.0);
927 break;
928 case LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY:
929 bModify = true;
930 [[fallthrough]];
931 case LOK_CALLBACK_DOCUMENT_PASSWORD:
933 char const*const pURL(pCallback->m_aPayload.c_str());
934 g_signal_emit (pCallback->m_pDocView, doc_view_signals[PASSWORD_REQUIRED], 0, pURL, bModify);
936 break;
937 case LOK_CALLBACK_ERROR:
939 reportError(pCallback->m_pDocView, pCallback->m_aPayload);
941 break;
942 case LOK_CALLBACK_SIGNATURE_STATUS:
944 // TODO
946 break;
947 default:
948 g_assert(false);
949 break;
951 delete pCallback;
953 return G_SOURCE_REMOVE;
956 static void
957 globalCallbackWorker(int nType, const char* pPayload, void* pData)
959 LOKDocView* pDocView = LOK_DOC_VIEW (pData);
961 CallbackData* pCallback = new CallbackData(nType, pPayload ? pPayload : "(nil)", pDocView);
962 g_info("LOKDocView_Impl::globalCallbackWorkerImpl: %s, '%s'", lokCallbackTypeToString(nType), pPayload);
963 gdk_threads_add_idle(globalCallback, pCallback);
966 static GdkRectangle
967 payloadToRectangle (LOKDocView* pDocView, const char* pPayload)
969 LOKDocViewPrivate& priv = getPrivate(pDocView);
970 GdkRectangle aRet;
971 // x, y, width, height, part number.
972 gchar** ppCoordinates = g_strsplit(pPayload, ", ", 5);
973 gchar** ppCoordinate = ppCoordinates;
975 aRet.width = aRet.height = aRet.x = aRet.y = 0;
977 if (!*ppCoordinate)
978 return aRet;
979 aRet.x = atoi(*ppCoordinate);
980 if (aRet.x < 0)
981 aRet.x = 0;
982 ++ppCoordinate;
983 if (!*ppCoordinate)
984 return aRet;
985 aRet.y = atoi(*ppCoordinate);
986 if (aRet.y < 0)
987 aRet.y = 0;
988 ++ppCoordinate;
989 if (!*ppCoordinate)
990 return aRet;
991 long l = atol(*ppCoordinate);
992 if (l > std::numeric_limits<int>::max())
993 aRet.width = std::numeric_limits<int>::max();
994 else
995 aRet.width = l;
996 if (aRet.x + aRet.width > priv->m_nDocumentWidthTwips)
997 aRet.width = priv->m_nDocumentWidthTwips - aRet.x;
998 ++ppCoordinate;
999 if (!*ppCoordinate)
1000 return aRet;
1001 l = atol(*ppCoordinate);
1002 if (l > std::numeric_limits<int>::max())
1003 aRet.height = std::numeric_limits<int>::max();
1004 else
1005 aRet.height = l;
1006 if (aRet.y + aRet.height > priv->m_nDocumentHeightTwips)
1007 aRet.height = priv->m_nDocumentHeightTwips - aRet.y;
1008 g_strfreev(ppCoordinates);
1010 return aRet;
1013 static std::vector<GdkRectangle>
1014 payloadToRectangles(LOKDocView* pDocView, const char* pPayload)
1016 std::vector<GdkRectangle> aRet;
1018 if (g_strcmp0(pPayload, "EMPTY") == 0)
1019 return aRet;
1021 gchar** ppRectangles = g_strsplit(pPayload, "; ", 0);
1022 for (gchar** ppRectangle = ppRectangles; *ppRectangle; ++ppRectangle)
1023 aRet.push_back(payloadToRectangle(pDocView, *ppRectangle));
1024 g_strfreev(ppRectangles);
1026 return aRet;
1030 static void
1031 setTilesInvalid (LOKDocView* pDocView, const GdkRectangle& rRectangle)
1033 LOKDocViewPrivate& priv = getPrivate(pDocView);
1034 GdkRectangle aRectanglePixels;
1035 GdkPoint aStart, aEnd;
1037 aRectanglePixels.x = twipToPixel(rRectangle.x, priv->m_fZoom);
1038 aRectanglePixels.y = twipToPixel(rRectangle.y, priv->m_fZoom);
1039 aRectanglePixels.width = twipToPixel(rRectangle.width, priv->m_fZoom);
1040 aRectanglePixels.height = twipToPixel(rRectangle.height, priv->m_fZoom);
1042 aStart.x = aRectanglePixels.y / nTileSizePixels;
1043 aStart.y = aRectanglePixels.x / nTileSizePixels;
1044 aEnd.x = (aRectanglePixels.y + aRectanglePixels.height + nTileSizePixels) / nTileSizePixels;
1045 aEnd.y = (aRectanglePixels.x + aRectanglePixels.width + nTileSizePixels) / nTileSizePixels;
1046 for (int i = aStart.x; i < aEnd.x; i++)
1048 for (int j = aStart.y; j < aEnd.y; j++)
1050 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
1051 priv->m_pTileBuffer->setInvalid(i, j, priv->m_fZoom, task, priv->lokThreadPool);
1052 g_object_unref(task);
1057 static gboolean
1058 callback (gpointer pData)
1060 CallbackData* pCallback = static_cast<CallbackData*>(pData);
1061 LOKDocView* pDocView = LOK_DOC_VIEW (pCallback->m_pDocView);
1062 LOKDocViewPrivate& priv = getPrivate(pDocView);
1064 //callback registered before the widget was destroyed.
1065 //Use existence of lokThreadPool as flag it was torn down
1066 if (!priv->lokThreadPool)
1068 delete pCallback;
1069 return G_SOURCE_REMOVE;
1072 switch (pCallback->m_nType)
1074 case LOK_CALLBACK_INVALIDATE_TILES:
1076 if (pCallback->m_aPayload.compare(0, 5, "EMPTY") != 0) // payload doesn't start with "EMPTY"
1078 GdkRectangle aRectangle = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1079 setTilesInvalid(pDocView, aRectangle);
1081 else
1082 priv->m_pTileBuffer->resetAllTiles();
1084 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1086 break;
1087 case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
1090 std::stringstream aStream(pCallback->m_aPayload);
1091 boost::property_tree::ptree aTree;
1092 boost::property_tree::read_json(aStream, aTree);
1093 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1094 int nViewId = aTree.get<int>("viewId");
1096 priv->m_aVisibleCursor = payloadToRectangle(pDocView, rRectangle.c_str());
1097 priv->m_bCursorOverlayVisible = true;
1098 std::cerr << nViewId;
1099 std::cerr << priv->m_nViewId;
1100 if(nViewId == priv->m_nViewId)
1102 g_signal_emit(pDocView, doc_view_signals[CURSOR_CHANGED], 0,
1103 priv->m_aVisibleCursor.x,
1104 priv->m_aVisibleCursor.y,
1105 priv->m_aVisibleCursor.width,
1106 priv->m_aVisibleCursor.height);
1108 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1110 break;
1111 case LOK_CALLBACK_TEXT_SELECTION:
1113 priv->m_aTextSelectionRectangles = payloadToRectangles(pDocView, pCallback->m_aPayload.c_str());
1114 gboolean bIsTextSelected = !priv->m_aTextSelectionRectangles.empty();
1115 // In case the selection is empty, then we get no LOK_CALLBACK_TEXT_SELECTION_START/END events.
1116 if (!bIsTextSelected)
1118 memset(&priv->m_aTextSelectionStart, 0, sizeof(priv->m_aTextSelectionStart));
1119 memset(&priv->m_aHandleStartRect, 0, sizeof(priv->m_aHandleStartRect));
1120 memset(&priv->m_aTextSelectionEnd, 0, sizeof(priv->m_aTextSelectionEnd));
1121 memset(&priv->m_aHandleEndRect, 0, sizeof(priv->m_aHandleEndRect));
1123 else
1124 memset(&priv->m_aHandleMiddleRect, 0, sizeof(priv->m_aHandleMiddleRect));
1126 g_signal_emit(pDocView, doc_view_signals[TEXT_SELECTION], 0, bIsTextSelected);
1127 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1129 break;
1130 case LOK_CALLBACK_TEXT_SELECTION_START:
1132 priv->m_aTextSelectionStart = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1134 break;
1135 case LOK_CALLBACK_TEXT_SELECTION_END:
1137 priv->m_aTextSelectionEnd = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1139 break;
1140 case LOK_CALLBACK_CURSOR_VISIBLE:
1142 priv->m_bCursorVisible = pCallback->m_aPayload == "true";
1144 break;
1145 case LOK_CALLBACK_MOUSE_POINTER:
1147 // We do not want the cursor to get changed in view-only mode
1148 if (priv->m_bEdit)
1150 // The gtk docs claim that most css cursors should be supported, however
1151 // on my system at least this is not true and many cursors are unsupported.
1152 // In this case pCursor = null, which results in the default cursor
1153 // being set.
1154 GdkCursor* pCursor = gdk_cursor_new_from_name(gtk_widget_get_display(GTK_WIDGET(pDocView)),
1155 pCallback->m_aPayload.c_str());
1156 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(pDocView)), pCursor);
1159 break;
1160 case LOK_CALLBACK_GRAPHIC_SELECTION:
1162 if (pCallback->m_aPayload != "EMPTY")
1163 priv->m_aGraphicSelection = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1164 else
1165 memset(&priv->m_aGraphicSelection, 0, sizeof(priv->m_aGraphicSelection));
1166 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1168 break;
1169 case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
1171 std::stringstream aStream(pCallback->m_aPayload);
1172 boost::property_tree::ptree aTree;
1173 boost::property_tree::read_json(aStream, aTree);
1174 int nViewId = aTree.get<int>("viewId");
1175 int nPart = aTree.get<int>("part");
1176 const std::string& rRectangle = aTree.get<std::string>("selection");
1177 if (rRectangle != "EMPTY")
1178 priv->m_aGraphicViewSelections[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1179 else
1181 auto it = priv->m_aGraphicViewSelections.find(nViewId);
1182 if (it != priv->m_aGraphicViewSelections.end())
1183 priv->m_aGraphicViewSelections.erase(it);
1185 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1186 break;
1188 break;
1189 case LOK_CALLBACK_CELL_CURSOR:
1191 if (pCallback->m_aPayload != "EMPTY")
1192 priv->m_aCellCursor = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1193 else
1194 memset(&priv->m_aCellCursor, 0, sizeof(priv->m_aCellCursor));
1195 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1197 break;
1198 case LOK_CALLBACK_HYPERLINK_CLICKED:
1200 hyperlinkClicked(pDocView, pCallback->m_aPayload);
1202 break;
1203 case LOK_CALLBACK_STATE_CHANGED:
1205 commandChanged(pDocView, pCallback->m_aPayload);
1207 break;
1208 case LOK_CALLBACK_SEARCH_NOT_FOUND:
1210 searchNotFound(pDocView, pCallback->m_aPayload);
1212 break;
1213 case LOK_CALLBACK_DOCUMENT_SIZE_CHANGED:
1215 if (!pCallback->m_aPayload.empty())
1216 payloadToSize(pCallback->m_aPayload.c_str(), priv->m_nDocumentWidthTwips, priv->m_nDocumentHeightTwips);
1217 else
1218 priv->m_pDocument->pClass->getDocumentSize(priv->m_pDocument, &priv->m_nDocumentWidthTwips, &priv->m_nDocumentHeightTwips);
1220 gtk_widget_set_size_request(GTK_WIDGET(pDocView),
1221 twipToPixel(priv->m_nDocumentWidthTwips, priv->m_fZoom),
1222 twipToPixel(priv->m_nDocumentHeightTwips, priv->m_fZoom));
1224 g_signal_emit(pDocView, doc_view_signals[SIZE_CHANGED], 0, nullptr);
1226 break;
1227 case LOK_CALLBACK_SET_PART:
1229 setPart(pDocView, pCallback->m_aPayload);
1231 break;
1232 case LOK_CALLBACK_SEARCH_RESULT_SELECTION:
1234 boost::property_tree::ptree aTree;
1235 std::stringstream aStream(pCallback->m_aPayload);
1236 boost::property_tree::read_json(aStream, aTree);
1237 int nCount = aTree.get_child("searchResultSelection").size();
1238 searchResultCount(pDocView, std::to_string(nCount));
1240 break;
1241 case LOK_CALLBACK_UNO_COMMAND_RESULT:
1243 commandResult(pDocView, pCallback->m_aPayload);
1245 break;
1246 case LOK_CALLBACK_CELL_ADDRESS:
1248 addressChanged(pDocView, pCallback->m_aPayload);
1250 break;
1251 case LOK_CALLBACK_CELL_FORMULA:
1253 formulaChanged(pDocView, pCallback->m_aPayload);
1255 break;
1256 case LOK_CALLBACK_ERROR:
1258 reportError(pDocView, pCallback->m_aPayload);
1260 break;
1261 case LOK_CALLBACK_CONTEXT_MENU:
1263 // TODO: Implement me
1264 break;
1266 case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
1268 std::stringstream aStream(pCallback->m_aPayload);
1269 boost::property_tree::ptree aTree;
1270 boost::property_tree::read_json(aStream, aTree);
1271 int nViewId = aTree.get<int>("viewId");
1272 int nPart = aTree.get<int>("part");
1273 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1274 priv->m_aViewCursors[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1275 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1276 break;
1278 case LOK_CALLBACK_TEXT_VIEW_SELECTION:
1280 std::stringstream aStream(pCallback->m_aPayload);
1281 boost::property_tree::ptree aTree;
1282 boost::property_tree::read_json(aStream, aTree);
1283 int nViewId = aTree.get<int>("viewId");
1284 int nPart = aTree.get<int>("part");
1285 const std::string& rSelection = aTree.get<std::string>("selection");
1286 priv->m_aTextViewSelectionRectangles[nViewId] = ViewRectangles(nPart, payloadToRectangles(pDocView, rSelection.c_str()));
1287 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1288 break;
1290 case LOK_CALLBACK_VIEW_CURSOR_VISIBLE:
1292 std::stringstream aStream(pCallback->m_aPayload);
1293 boost::property_tree::ptree aTree;
1294 boost::property_tree::read_json(aStream, aTree);
1295 int nViewId = aTree.get<int>("viewId");
1296 const std::string& rVisible = aTree.get<std::string>("visible");
1297 priv->m_aViewCursorVisibilities[nViewId] = rVisible == "true";
1298 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1299 break;
1301 break;
1302 case LOK_CALLBACK_CELL_VIEW_CURSOR:
1304 std::stringstream aStream(pCallback->m_aPayload);
1305 boost::property_tree::ptree aTree;
1306 boost::property_tree::read_json(aStream, aTree);
1307 int nViewId = aTree.get<int>("viewId");
1308 int nPart = aTree.get<int>("part");
1309 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1310 if (rRectangle != "EMPTY")
1311 priv->m_aCellViewCursors[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1312 else
1314 auto it = priv->m_aCellViewCursors.find(nViewId);
1315 if (it != priv->m_aCellViewCursors.end())
1316 priv->m_aCellViewCursors.erase(it);
1318 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1319 break;
1321 case LOK_CALLBACK_VIEW_LOCK:
1323 std::stringstream aStream(pCallback->m_aPayload);
1324 boost::property_tree::ptree aTree;
1325 boost::property_tree::read_json(aStream, aTree);
1326 int nViewId = aTree.get<int>("viewId");
1327 int nPart = aTree.get<int>("part");
1328 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1329 if (rRectangle != "EMPTY")
1330 priv->m_aViewLockRectangles[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1331 else
1333 auto it = priv->m_aViewLockRectangles.find(nViewId);
1334 if (it != priv->m_aViewLockRectangles.end())
1335 priv->m_aViewLockRectangles.erase(it);
1337 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1338 break;
1340 case LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED:
1342 break;
1344 case LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED:
1346 break;
1348 case LOK_CALLBACK_COMMENT:
1349 g_signal_emit(pCallback->m_pDocView, doc_view_signals[COMMENT], 0, pCallback->m_aPayload.c_str());
1350 break;
1351 case LOK_CALLBACK_RULER_UPDATE:
1352 g_signal_emit(pCallback->m_pDocView, doc_view_signals[RULER], 0, pCallback->m_aPayload.c_str());
1353 break;
1354 case LOK_CALLBACK_WINDOW:
1355 g_signal_emit(pCallback->m_pDocView, doc_view_signals[WINDOW], 0, pCallback->m_aPayload.c_str());
1356 break;
1357 case LOK_CALLBACK_INVALIDATE_HEADER:
1358 g_signal_emit(pCallback->m_pDocView, doc_view_signals[INVALIDATE_HEADER], 0, pCallback->m_aPayload.c_str());
1359 break;
1360 case LOK_CALLBACK_CLIPBOARD_CHANGED:
1361 case LOK_CALLBACK_CONTEXT_CHANGED:
1362 case LOK_CALLBACK_CELL_SELECTION_AREA:
1363 case LOK_CALLBACK_CELL_AUTO_FILL_AREA:
1364 case LOK_CALLBACK_TABLE_SELECTED:
1365 break; // TODO
1366 default:
1367 g_assert(false);
1368 break;
1370 delete pCallback;
1372 return G_SOURCE_REMOVE;
1375 static void callbackWorker (int nType, const char* pPayload, void* pData)
1377 LOKDocView* pDocView = LOK_DOC_VIEW (pData);
1379 CallbackData* pCallback = new CallbackData(nType, pPayload ? pPayload : "(nil)", pDocView);
1380 LOKDocViewPrivate& priv = getPrivate(pDocView);
1381 std::stringstream ss;
1382 ss << "callbackWorker, view #" << priv->m_nViewId << ": " << lokCallbackTypeToString(nType) << ", '" << (pPayload ? pPayload : "(nil)") << "'";
1383 g_info("%s", ss.str().c_str());
1384 gdk_threads_add_idle(callback, pCallback);
1387 static void
1388 renderHandle(LOKDocView* pDocView,
1389 cairo_t* pCairo,
1390 const GdkRectangle& rCursor,
1391 cairo_surface_t* pHandle,
1392 GdkRectangle& rRectangle)
1394 LOKDocViewPrivate& priv = getPrivate(pDocView);
1395 GdkPoint aCursorBottom;
1396 int nHandleWidth, nHandleHeight;
1397 double fHandleScale;
1399 nHandleWidth = cairo_image_surface_get_width(pHandle);
1400 nHandleHeight = cairo_image_surface_get_height(pHandle);
1401 // We want to scale down the handle, so that its height is the same as the cursor caret.
1402 fHandleScale = twipToPixel(rCursor.height, priv->m_fZoom) / nHandleHeight;
1403 // We want the top center of the handle bitmap to be at the bottom center of the cursor rectangle.
1404 aCursorBottom.x = twipToPixel(rCursor.x, priv->m_fZoom) + twipToPixel(rCursor.width, priv->m_fZoom) / 2 - (nHandleWidth * fHandleScale) / 2;
1405 aCursorBottom.y = twipToPixel(rCursor.y, priv->m_fZoom) + twipToPixel(rCursor.height, priv->m_fZoom);
1407 cairo_save (pCairo);
1408 cairo_translate(pCairo, aCursorBottom.x, aCursorBottom.y);
1409 cairo_scale(pCairo, fHandleScale, fHandleScale);
1410 cairo_set_source_surface(pCairo, pHandle, 0, 0);
1411 cairo_paint(pCairo);
1412 cairo_restore (pCairo);
1414 rRectangle.x = aCursorBottom.x;
1415 rRectangle.y = aCursorBottom.y;
1416 rRectangle.width = nHandleWidth * fHandleScale;
1417 rRectangle.height = nHandleHeight * fHandleScale;
1420 /// Renders handles around an rSelection rectangle on pCairo.
1421 static void
1422 renderGraphicHandle(LOKDocView* pDocView,
1423 cairo_t* pCairo,
1424 const GdkRectangle& rSelection,
1425 const GdkRGBA& rColor)
1427 LOKDocViewPrivate& priv = getPrivate(pDocView);
1428 int nHandleWidth = 9, nHandleHeight = 9;
1429 GdkRectangle aSelection;
1431 aSelection.x = twipToPixel(rSelection.x, priv->m_fZoom);
1432 aSelection.y = twipToPixel(rSelection.y, priv->m_fZoom);
1433 aSelection.width = twipToPixel(rSelection.width, priv->m_fZoom);
1434 aSelection.height = twipToPixel(rSelection.height, priv->m_fZoom);
1436 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
1438 int x = aSelection.x, y = aSelection.y;
1440 switch (i)
1442 case 0: // top-left
1443 break;
1444 case 1: // top-middle
1445 x += aSelection.width / 2;
1446 break;
1447 case 2: // top-right
1448 x += aSelection.width;
1449 break;
1450 case 3: // middle-left
1451 y += aSelection.height / 2;
1452 break;
1453 case 4: // middle-right
1454 x += aSelection.width;
1455 y += aSelection.height / 2;
1456 break;
1457 case 5: // bottom-left
1458 y += aSelection.height;
1459 break;
1460 case 6: // bottom-middle
1461 x += aSelection.width / 2;
1462 y += aSelection.height;
1463 break;
1464 case 7: // bottom-right
1465 x += aSelection.width;
1466 y += aSelection.height;
1467 break;
1470 // Center the handle.
1471 x -= nHandleWidth / 2;
1472 y -= nHandleHeight / 2;
1474 priv->m_aGraphicHandleRects[i].x = x;
1475 priv->m_aGraphicHandleRects[i].y = y;
1476 priv->m_aGraphicHandleRects[i].width = nHandleWidth;
1477 priv->m_aGraphicHandleRects[i].height = nHandleHeight;
1479 cairo_set_source_rgb(pCairo, rColor.red, rColor.green, rColor.blue);
1480 cairo_rectangle(pCairo, x, y, nHandleWidth, nHandleHeight);
1481 cairo_fill(pCairo);
1485 /// Finishes the paint tile operation and returns the result, if any
1486 static gpointer
1487 paintTileFinish(LOKDocView* pDocView, GAsyncResult* res, GError **error)
1489 GTask* task = G_TASK(res);
1491 g_return_val_if_fail(LOK_IS_DOC_VIEW(pDocView), nullptr);
1492 g_return_val_if_fail(g_task_is_valid(res, pDocView), nullptr);
1493 g_return_val_if_fail(error == nullptr || *error == nullptr, nullptr);
1495 return g_task_propagate_pointer(task, error);
1498 /// Callback called in the main UI thread when paintTileInThread in LOK thread has finished
1499 static void
1500 paintTileCallback(GObject* sourceObject, GAsyncResult* res, gpointer userData)
1502 LOKDocView* pDocView = LOK_DOC_VIEW(sourceObject);
1503 LOKDocViewPrivate& priv = getPrivate(pDocView);
1504 LOEvent* pLOEvent = static_cast<LOEvent*>(userData);
1505 std::unique_ptr<TileBuffer>& buffer = priv->m_pTileBuffer;
1506 int index = pLOEvent->m_nPaintTileX * buffer->m_nWidth + pLOEvent->m_nPaintTileY;
1507 GError* error;
1509 error = nullptr;
1510 cairo_surface_t* pSurface = static_cast<cairo_surface_t*>(paintTileFinish(pDocView, res, &error));
1511 if (error != nullptr)
1513 if (error->domain == LOK_TILEBUFFER_ERROR &&
1514 error->code == LOK_TILEBUFFER_CHANGED)
1515 g_info("Skipping paint tile request because corresponding"
1516 "tile buffer has been destroyed");
1517 else
1518 g_warning("Unable to get painted GdkPixbuf: %s", error->message);
1519 g_error_free(error);
1520 return;
1523 buffer->m_mTiles[index].setSurface(pSurface);
1524 buffer->m_mTiles[index].valid = true;
1525 gdk_threads_add_idle(queueDraw, GTK_WIDGET(pDocView));
1527 cairo_surface_destroy(pSurface);
1531 static gboolean
1532 renderDocument(LOKDocView* pDocView, cairo_t* pCairo)
1534 LOKDocViewPrivate& priv = getPrivate(pDocView);
1535 GdkRectangle aVisibleArea;
1536 long nDocumentWidthPixels = twipToPixel(priv->m_nDocumentWidthTwips, priv->m_fZoom);
1537 long nDocumentHeightPixels = twipToPixel(priv->m_nDocumentHeightTwips, priv->m_fZoom);
1538 // Total number of rows / columns in this document.
1539 guint nRows = ceil(static_cast<double>(nDocumentHeightPixels) / nTileSizePixels);
1540 guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixels);
1542 gdk_cairo_get_clip_rectangle (pCairo, &aVisibleArea);
1543 aVisibleArea.x = pixelToTwip (aVisibleArea.x, priv->m_fZoom);
1544 aVisibleArea.y = pixelToTwip (aVisibleArea.y, priv->m_fZoom);
1545 aVisibleArea.width = pixelToTwip (aVisibleArea.width, priv->m_fZoom);
1546 aVisibleArea.height = pixelToTwip (aVisibleArea.height, priv->m_fZoom);
1548 // Render the tiles.
1549 for (guint nRow = 0; nRow < nRows; ++nRow)
1551 for (guint nColumn = 0; nColumn < nColumns; ++nColumn)
1553 GdkRectangle aTileRectangleTwips, aTileRectanglePixels;
1554 bool bPaint = true;
1556 // Determine size of the tile: the rightmost/bottommost tiles may
1557 // be smaller, and we need the size to decide if we need to repaint.
1558 if (nColumn == nColumns - 1)
1559 aTileRectanglePixels.width = nDocumentWidthPixels - nColumn * nTileSizePixels;
1560 else
1561 aTileRectanglePixels.width = nTileSizePixels;
1562 if (nRow == nRows - 1)
1563 aTileRectanglePixels.height = nDocumentHeightPixels - nRow * nTileSizePixels;
1564 else
1565 aTileRectanglePixels.height = nTileSizePixels;
1567 // Determine size and position of the tile in document coordinates,
1568 // so we can decide if we can skip painting for partial rendering.
1569 aTileRectangleTwips.x = pixelToTwip(nTileSizePixels, priv->m_fZoom) * nColumn;
1570 aTileRectangleTwips.y = pixelToTwip(nTileSizePixels, priv->m_fZoom) * nRow;
1571 aTileRectangleTwips.width = pixelToTwip(aTileRectanglePixels.width, priv->m_fZoom);
1572 aTileRectangleTwips.height = pixelToTwip(aTileRectanglePixels.height, priv->m_fZoom);
1574 if (!gdk_rectangle_intersect(&aVisibleArea, &aTileRectangleTwips, nullptr))
1575 bPaint = false;
1577 if (bPaint)
1579 LOEvent* pLOEvent = new LOEvent(LOK_PAINT_TILE);
1580 pLOEvent->m_nPaintTileX = nRow;
1581 pLOEvent->m_nPaintTileY = nColumn;
1582 pLOEvent->m_fPaintTileZoom = priv->m_fZoom;
1583 pLOEvent->m_pTileBuffer = &*priv->m_pTileBuffer;
1584 GTask* task = g_task_new(pDocView, nullptr, paintTileCallback, pLOEvent);
1585 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
1587 Tile& currentTile = priv->m_pTileBuffer->getTile(nRow, nColumn, task, priv->lokThreadPool);
1588 cairo_surface_t* pSurface = currentTile.getBuffer();
1589 cairo_set_source_surface(pCairo, pSurface,
1590 twipToPixel(aTileRectangleTwips.x, priv->m_fZoom),
1591 twipToPixel(aTileRectangleTwips.y, priv->m_fZoom));
1592 cairo_paint(pCairo);
1593 g_object_unref(task);
1598 return FALSE;
1601 static const GdkRGBA& getDarkColor(int nViewId, LOKDocViewPrivate& priv)
1603 static std::map<int, GdkRGBA> aColorMap;
1604 auto it = aColorMap.find(nViewId);
1605 if (it != aColorMap.end())
1606 return it->second;
1608 if (priv->m_eDocumentType == LOK_DOCTYPE_TEXT)
1610 char* pValues = priv->m_pDocument->pClass->getCommandValues(priv->m_pDocument, ".uno:TrackedChangeAuthors");
1611 std::stringstream aInfo;
1612 aInfo << "lok::Document::getCommandValues('.uno:TrackedChangeAuthors') returned '" << pValues << "'" << std::endl;
1613 g_info("%s", aInfo.str().c_str());
1615 std::stringstream aStream(pValues);
1616 boost::property_tree::ptree aTree;
1617 boost::property_tree::read_json(aStream, aTree);
1618 for (const auto& rValue : aTree.get_child("authors"))
1620 const std::string& rName = rValue.second.get<std::string>("name");
1621 guint32 nColor = rValue.second.get<guint32>("color");
1622 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};
1623 auto itAuthorViews = g_aAuthorViews.find(rName);
1624 if (itAuthorViews != g_aAuthorViews.end())
1625 aColorMap[itAuthorViews->second] = aColor;
1628 else
1630 // Based on tools/color.hxx, COL_AUTHOR1_DARK..COL_AUTHOR9_DARK.
1631 static std::vector<GdkRGBA> aColors =
1633 {(double(198))/255, (double(146))/255, (double(0))/255, 0},
1634 {(double(6))/255, (double(70))/255, (double(162))/255, 0},
1635 {(double(87))/255, (double(157))/255, (double(28))/255, 0},
1636 {(double(105))/255, (double(43))/255, (double(157))/255, 0},
1637 {(double(197))/255, (double(0))/255, (double(11))/255, 0},
1638 {(double(0))/255, (double(128))/255, (double(128))/255, 0},
1639 {(double(140))/255, (double(132))/255, (double(0))/255, 0},
1640 {(double(43))/255, (double(85))/255, (double(107))/255, 0},
1641 {(double(209))/255, (double(118))/255, (double(0))/255, 0},
1643 static int nColorCounter = 0;
1644 GdkRGBA aColor = aColors[nColorCounter++ % aColors.size()];
1645 aColorMap[nViewId] = aColor;
1647 assert(aColorMap.find(nViewId) != aColorMap.end());
1648 return aColorMap[nViewId];
1651 static gboolean
1652 renderOverlay(LOKDocView* pDocView, cairo_t* pCairo)
1654 LOKDocViewPrivate& priv = getPrivate(pDocView);
1656 if (priv->m_bEdit && priv->m_bCursorVisible && priv->m_bCursorOverlayVisible && !isEmptyRectangle(priv->m_aVisibleCursor))
1658 if (priv->m_aVisibleCursor.width < 30)
1659 // Set a minimal width if it would be 0.
1660 priv->m_aVisibleCursor.width = 30;
1662 cairo_set_source_rgb(pCairo, 0, 0, 0);
1663 cairo_rectangle(pCairo,
1664 twipToPixel(priv->m_aVisibleCursor.x, priv->m_fZoom),
1665 twipToPixel(priv->m_aVisibleCursor.y, priv->m_fZoom),
1666 twipToPixel(priv->m_aVisibleCursor.width, priv->m_fZoom),
1667 twipToPixel(priv->m_aVisibleCursor.height, priv->m_fZoom));
1668 cairo_fill(pCairo);
1671 // View cursors: they do not blink and are colored.
1672 if (priv->m_bEdit && !priv->m_aViewCursors.empty())
1674 for (auto& rPair : priv->m_aViewCursors)
1676 auto itVisibility = priv->m_aViewCursorVisibilities.find(rPair.first);
1677 if (itVisibility != priv->m_aViewCursorVisibilities.end() && !itVisibility->second)
1678 continue;
1680 // Show view cursors when in Writer or when the part matches.
1681 if (rPair.second.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
1682 continue;
1684 GdkRectangle& rCursor = rPair.second.m_aRectangle;
1685 if (rCursor.width < 30)
1686 // Set a minimal width if it would be 0.
1687 rCursor.width = 30;
1689 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1690 cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
1691 cairo_rectangle(pCairo,
1692 twipToPixel(rCursor.x, priv->m_fZoom),
1693 twipToPixel(rCursor.y, priv->m_fZoom),
1694 twipToPixel(rCursor.width, priv->m_fZoom),
1695 twipToPixel(rCursor.height, priv->m_fZoom));
1696 cairo_fill(pCairo);
1700 if (priv->m_bEdit && priv->m_bCursorVisible && !isEmptyRectangle(priv->m_aVisibleCursor) && priv->m_aTextSelectionRectangles.empty())
1702 // Have a cursor, but no selection: we need the middle handle.
1703 gchar* handleMiddlePath = g_strconcat (priv->m_aLOPath.c_str(), CURSOR_HANDLE_DIR, "handle_image_middle.png", nullptr);
1704 if (!priv->m_pHandleMiddle)
1706 priv->m_pHandleMiddle = cairo_image_surface_create_from_png(handleMiddlePath);
1707 assert(cairo_surface_status(priv->m_pHandleMiddle) == CAIRO_STATUS_SUCCESS);
1709 g_free (handleMiddlePath);
1710 renderHandle(pDocView, pCairo, priv->m_aVisibleCursor, priv->m_pHandleMiddle, priv->m_aHandleMiddleRect);
1713 if (!priv->m_aTextSelectionRectangles.empty())
1715 for (const GdkRectangle& rRectangle : priv->m_aTextSelectionRectangles)
1717 // Blue with 75% transparency.
1718 cairo_set_source_rgba(pCairo, (double(0x43))/255, (double(0xac))/255, (double(0xe8))/255, 0.25);
1719 cairo_rectangle(pCairo,
1720 twipToPixel(rRectangle.x, priv->m_fZoom),
1721 twipToPixel(rRectangle.y, priv->m_fZoom),
1722 twipToPixel(rRectangle.width, priv->m_fZoom),
1723 twipToPixel(rRectangle.height, priv->m_fZoom));
1724 cairo_fill(pCairo);
1727 // Handles
1728 if (!isEmptyRectangle(priv->m_aTextSelectionStart))
1730 // Have a start position: we need a start handle.
1731 gchar* handleStartPath = g_strconcat (priv->m_aLOPath.c_str(), CURSOR_HANDLE_DIR, "handle_image_start.png", nullptr);
1732 if (!priv->m_pHandleStart)
1734 priv->m_pHandleStart = cairo_image_surface_create_from_png(handleStartPath);
1735 assert(cairo_surface_status(priv->m_pHandleStart) == CAIRO_STATUS_SUCCESS);
1737 renderHandle(pDocView, pCairo, priv->m_aTextSelectionStart, priv->m_pHandleStart, priv->m_aHandleStartRect);
1738 g_free (handleStartPath);
1740 if (!isEmptyRectangle(priv->m_aTextSelectionEnd))
1742 // Have a start position: we need an end handle.
1743 gchar* handleEndPath = g_strconcat (priv->m_aLOPath.c_str(), CURSOR_HANDLE_DIR, "handle_image_end.png", nullptr);
1744 if (!priv->m_pHandleEnd)
1746 priv->m_pHandleEnd = cairo_image_surface_create_from_png(handleEndPath);
1747 assert(cairo_surface_status(priv->m_pHandleEnd) == CAIRO_STATUS_SUCCESS);
1749 renderHandle(pDocView, pCairo, priv->m_aTextSelectionEnd, priv->m_pHandleEnd, priv->m_aHandleEndRect);
1750 g_free (handleEndPath);
1754 // Selections of other views.
1755 for (const auto& rPair : priv->m_aTextViewSelectionRectangles)
1757 if (rPair.second.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
1758 continue;
1760 for (const GdkRectangle& rRectangle : rPair.second.m_aRectangles)
1762 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1763 // 75% transparency.
1764 cairo_set_source_rgba(pCairo, rDark.red, rDark.green, rDark.blue, 0.25);
1765 cairo_rectangle(pCairo,
1766 twipToPixel(rRectangle.x, priv->m_fZoom),
1767 twipToPixel(rRectangle.y, priv->m_fZoom),
1768 twipToPixel(rRectangle.width, priv->m_fZoom),
1769 twipToPixel(rRectangle.height, priv->m_fZoom));
1770 cairo_fill(pCairo);
1774 if (!isEmptyRectangle(priv->m_aGraphicSelection))
1776 GdkRGBA const aBlack{0, 0, 0, 0};
1777 renderGraphicHandle(pDocView, pCairo, priv->m_aGraphicSelection, aBlack);
1780 // Graphic selections of other views.
1781 for (const auto& rPair : priv->m_aGraphicViewSelections)
1783 const ViewRectangle& rRectangle = rPair.second;
1784 if (rRectangle.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
1785 continue;
1787 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1788 renderGraphicHandle(pDocView, pCairo, rRectangle.m_aRectangle, rDark);
1791 // Draw the cell cursor.
1792 if (!isEmptyRectangle(priv->m_aCellCursor))
1794 cairo_set_source_rgb(pCairo, 0, 0, 0);
1795 cairo_rectangle(pCairo,
1796 twipToPixel(priv->m_aCellCursor.x, priv->m_fZoom),
1797 twipToPixel(priv->m_aCellCursor.y, priv->m_fZoom),
1798 twipToPixel(priv->m_aCellCursor.width, priv->m_fZoom),
1799 twipToPixel(priv->m_aCellCursor.height, priv->m_fZoom));
1800 cairo_set_line_width(pCairo, 2.0);
1801 cairo_stroke(pCairo);
1804 // Cell view cursors: they are colored.
1805 for (const auto& rPair : priv->m_aCellViewCursors)
1807 const ViewRectangle& rCursor = rPair.second;
1808 if (rCursor.m_nPart != priv->m_nPartId)
1809 continue;
1811 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1812 cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
1813 cairo_rectangle(pCairo,
1814 twipToPixel(rCursor.m_aRectangle.x, priv->m_fZoom),
1815 twipToPixel(rCursor.m_aRectangle.y, priv->m_fZoom),
1816 twipToPixel(rCursor.m_aRectangle.width, priv->m_fZoom),
1817 twipToPixel(rCursor.m_aRectangle.height, priv->m_fZoom));
1818 cairo_set_line_width(pCairo, 2.0);
1819 cairo_stroke(pCairo);
1822 // View locks: they are colored.
1823 for (const auto& rPair : priv->m_aViewLockRectangles)
1825 const ViewRectangle& rRectangle = rPair.second;
1826 if (rRectangle.m_nPart != priv->m_nPartId)
1827 continue;
1829 // Draw a rectangle.
1830 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1831 cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
1832 cairo_rectangle(pCairo,
1833 twipToPixel(rRectangle.m_aRectangle.x, priv->m_fZoom),
1834 twipToPixel(rRectangle.m_aRectangle.y, priv->m_fZoom),
1835 twipToPixel(rRectangle.m_aRectangle.width, priv->m_fZoom),
1836 twipToPixel(rRectangle.m_aRectangle.height, priv->m_fZoom));
1837 cairo_set_line_width(pCairo, 2.0);
1838 cairo_stroke(pCairo);
1840 // And a lock.
1841 cairo_rectangle(pCairo,
1842 twipToPixel(rRectangle.m_aRectangle.x + rRectangle.m_aRectangle.width, priv->m_fZoom) - 25,
1843 twipToPixel(rRectangle.m_aRectangle.y + rRectangle.m_aRectangle.height, priv->m_fZoom) - 15,
1845 10);
1846 cairo_fill(pCairo);
1847 cairo_arc(pCairo,
1848 twipToPixel(rRectangle.m_aRectangle.x + rRectangle.m_aRectangle.width, priv->m_fZoom) - 15,
1849 twipToPixel(rRectangle.m_aRectangle.y + rRectangle.m_aRectangle.height, priv->m_fZoom) - 15,
1851 180.0 * (M_PI/180.0),
1852 360.0 * (M_PI/180.0));
1853 cairo_stroke(pCairo);
1856 return FALSE;
1859 static gboolean
1860 lok_doc_view_signal_button(GtkWidget* pWidget, GdkEventButton* pEvent)
1862 LOKDocView* pDocView = LOK_DOC_VIEW (pWidget);
1863 LOKDocViewPrivate& priv = getPrivate(pDocView);
1864 GError* error = nullptr;
1866 g_info("LOKDocView_Impl::signalButton: %d, %d (in twips: %d, %d)",
1867 static_cast<int>(pEvent->x), static_cast<int>(pEvent->y),
1868 static_cast<int>(pixelToTwip(pEvent->x, priv->m_fZoom)),
1869 static_cast<int>(pixelToTwip(pEvent->y, priv->m_fZoom)));
1870 gtk_widget_grab_focus(GTK_WIDGET(pDocView));
1872 switch (pEvent->type)
1874 case GDK_BUTTON_PRESS:
1876 GdkRectangle aClick;
1877 aClick.x = pEvent->x;
1878 aClick.y = pEvent->y;
1879 aClick.width = 1;
1880 aClick.height = 1;
1882 if (handleTextSelectionOnButtonPress(aClick, pDocView))
1883 return FALSE;
1884 if (handleGraphicSelectionOnButtonPress(aClick, pDocView))
1885 return FALSE;
1887 int nCount = 1;
1888 if ((pEvent->time - priv->m_nLastButtonPressTime) < 250)
1889 nCount++;
1890 priv->m_nLastButtonPressTime = pEvent->time;
1891 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
1892 LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
1893 pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEBUTTONDOWN;
1894 pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
1895 pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
1896 pLOEvent->m_nPostMouseEventCount = nCount;
1897 switch (pEvent->button)
1899 case 1:
1900 pLOEvent->m_nPostMouseEventButton = MOUSE_LEFT;
1901 break;
1902 case 2:
1903 pLOEvent->m_nPostMouseEventButton = MOUSE_MIDDLE;
1904 break;
1905 case 3:
1906 pLOEvent->m_nPostMouseEventButton = MOUSE_RIGHT;
1907 break;
1909 pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
1910 priv->m_nLastButtonPressed = pLOEvent->m_nPostMouseEventButton;
1911 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
1913 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
1914 if (error != nullptr)
1916 g_warning("Unable to call LOK_POST_MOUSE_EVENT: %s", error->message);
1917 g_clear_error(&error);
1919 g_object_unref(task);
1920 break;
1922 case GDK_BUTTON_RELEASE:
1924 if (handleTextSelectionOnButtonRelease(pDocView))
1925 return FALSE;
1926 if (handleGraphicSelectionOnButtonRelease(pDocView, pEvent))
1927 return FALSE;
1929 int nCount = 1;
1930 if ((pEvent->time - priv->m_nLastButtonReleaseTime) < 250)
1931 nCount++;
1932 priv->m_nLastButtonReleaseTime = pEvent->time;
1933 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
1934 LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
1935 pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEBUTTONUP;
1936 pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
1937 pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
1938 pLOEvent->m_nPostMouseEventCount = nCount;
1939 switch (pEvent->button)
1941 case 1:
1942 pLOEvent->m_nPostMouseEventButton = MOUSE_LEFT;
1943 break;
1944 case 2:
1945 pLOEvent->m_nPostMouseEventButton = MOUSE_MIDDLE;
1946 break;
1947 case 3:
1948 pLOEvent->m_nPostMouseEventButton = MOUSE_RIGHT;
1949 break;
1951 pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
1952 priv->m_nLastButtonPressed = pLOEvent->m_nPostMouseEventButton;
1953 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
1955 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
1956 if (error != nullptr)
1958 g_warning("Unable to call LOK_POST_MOUSE_EVENT: %s", error->message);
1959 g_clear_error(&error);
1961 g_object_unref(task);
1962 break;
1964 default:
1965 break;
1967 return FALSE;
1970 static void
1971 getDragPoint(GdkRectangle* pHandle,
1972 GdkEventMotion* pEvent,
1973 GdkPoint* pPoint)
1975 GdkPoint aCursor, aHandle;
1977 // Center of the cursor rectangle: we know that it's above the handle.
1978 aCursor.x = pHandle->x + pHandle->width / 2;
1979 aCursor.y = pHandle->y - pHandle->height / 2;
1980 // Center of the handle rectangle.
1981 aHandle.x = pHandle->x + pHandle->width / 2;
1982 aHandle.y = pHandle->y + pHandle->height / 2;
1983 // Our target is the original cursor position + the dragged offset.
1984 pPoint->x = aCursor.x + (pEvent->x - aHandle.x);
1985 pPoint->y = aCursor.y + (pEvent->y - aHandle.y);
1988 static gboolean
1989 lok_doc_view_signal_motion (GtkWidget* pWidget, GdkEventMotion* pEvent)
1991 LOKDocView* pDocView = LOK_DOC_VIEW (pWidget);
1992 LOKDocViewPrivate& priv = getPrivate(pDocView);
1993 GdkPoint aPoint;
1994 GError* error = nullptr;
1996 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
1997 std::stringstream ss;
1998 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
1999 g_info("%s", ss.str().c_str());
2000 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2001 if (priv->m_bInDragMiddleHandle)
2003 g_info("lcl_signalMotion: dragging the middle handle");
2004 getDragPoint(&priv->m_aHandleMiddleRect, pEvent, &aPoint);
2005 priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_RESET, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
2006 return FALSE;
2008 if (priv->m_bInDragStartHandle)
2010 g_info("lcl_signalMotion: dragging the start handle");
2011 getDragPoint(&priv->m_aHandleStartRect, pEvent, &aPoint);
2012 priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_START, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
2013 return FALSE;
2015 if (priv->m_bInDragEndHandle)
2017 g_info("lcl_signalMotion: dragging the end handle");
2018 getDragPoint(&priv->m_aHandleEndRect, pEvent, &aPoint);
2019 priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_END, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
2020 return FALSE;
2022 aGuard.unlock();
2023 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
2025 if (priv->m_bInDragGraphicHandles[i])
2027 g_info("lcl_signalMotion: dragging the graphic handle #%d", i);
2028 return FALSE;
2031 if (priv->m_bInDragGraphicSelection)
2033 g_info("lcl_signalMotion: dragging the graphic selection");
2034 return FALSE;
2037 GdkRectangle aMotionInTwipsInTwips;
2038 aMotionInTwipsInTwips.x = pixelToTwip(pEvent->x, priv->m_fZoom);
2039 aMotionInTwipsInTwips.y = pixelToTwip(pEvent->y, priv->m_fZoom);
2040 aMotionInTwipsInTwips.width = 1;
2041 aMotionInTwipsInTwips.height = 1;
2042 if (gdk_rectangle_intersect(&aMotionInTwipsInTwips, &priv->m_aGraphicSelection, nullptr))
2044 g_info("lcl_signalMotion: start of drag graphic selection");
2045 priv->m_bInDragGraphicSelection = true;
2047 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2048 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
2049 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_START;
2050 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(pEvent->x, priv->m_fZoom);
2051 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(pEvent->y, priv->m_fZoom);
2052 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2054 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2055 if (error != nullptr)
2057 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
2058 g_clear_error(&error);
2060 g_object_unref(task);
2062 return FALSE;
2065 // Otherwise a mouse move, as on the desktop.
2067 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2068 LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
2069 pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEMOVE;
2070 pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
2071 pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
2072 pLOEvent->m_nPostMouseEventCount = 1;
2073 pLOEvent->m_nPostMouseEventButton = priv->m_nLastButtonPressed;
2074 pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
2076 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2078 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2079 if (error != nullptr)
2081 g_warning("Unable to call LOK_MOUSEEVENT_MOUSEMOVE: %s", error->message);
2082 g_clear_error(&error);
2084 g_object_unref(task);
2086 return FALSE;
2089 static void
2090 setGraphicSelectionInThread(gpointer data)
2092 GTask* task = G_TASK(data);
2093 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2094 LOKDocViewPrivate& priv = getPrivate(pDocView);
2095 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2097 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2098 std::stringstream ss;
2099 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2100 g_info("%s", ss.str().c_str());
2101 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2102 ss.str(std::string());
2103 ss << "lok::Document::setGraphicSelection(" << pLOEvent->m_nSetGraphicSelectionType;
2104 ss << ", " << pLOEvent->m_nSetGraphicSelectionX;
2105 ss << ", " << pLOEvent->m_nSetGraphicSelectionY << ")";
2106 g_info("%s", ss.str().c_str());
2107 priv->m_pDocument->pClass->setGraphicSelection(priv->m_pDocument,
2108 pLOEvent->m_nSetGraphicSelectionType,
2109 pLOEvent->m_nSetGraphicSelectionX,
2110 pLOEvent->m_nSetGraphicSelectionY);
2113 static void
2114 setClientZoomInThread(gpointer data)
2116 GTask* task = G_TASK(data);
2117 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2118 LOKDocViewPrivate& priv = getPrivate(pDocView);
2119 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2121 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2122 priv->m_pDocument->pClass->setClientZoom(priv->m_pDocument,
2123 pLOEvent->m_nTilePixelWidth,
2124 pLOEvent->m_nTilePixelHeight,
2125 pLOEvent->m_nTileTwipWidth,
2126 pLOEvent->m_nTileTwipHeight);
2129 static void
2130 postMouseEventInThread(gpointer data)
2132 GTask* task = G_TASK(data);
2133 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2134 LOKDocViewPrivate& priv = getPrivate(pDocView);
2135 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2137 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2138 std::stringstream ss;
2139 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2140 g_info("%s", ss.str().c_str());
2141 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2142 ss.str(std::string());
2143 ss << "lok::Document::postMouseEvent(" << pLOEvent->m_nPostMouseEventType;
2144 ss << ", " << pLOEvent->m_nPostMouseEventX;
2145 ss << ", " << pLOEvent->m_nPostMouseEventY;
2146 ss << ", " << pLOEvent->m_nPostMouseEventCount;
2147 ss << ", " << pLOEvent->m_nPostMouseEventButton;
2148 ss << ", " << pLOEvent->m_nPostMouseEventModifier << ")";
2149 g_info("%s", ss.str().c_str());
2150 priv->m_pDocument->pClass->postMouseEvent(priv->m_pDocument,
2151 pLOEvent->m_nPostMouseEventType,
2152 pLOEvent->m_nPostMouseEventX,
2153 pLOEvent->m_nPostMouseEventY,
2154 pLOEvent->m_nPostMouseEventCount,
2155 pLOEvent->m_nPostMouseEventButton,
2156 pLOEvent->m_nPostMouseEventModifier);
2159 static void
2160 openDocumentInThread (gpointer data)
2162 GTask* task = G_TASK(data);
2163 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2164 LOKDocViewPrivate& priv = getPrivate(pDocView);
2166 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2167 if ( priv->m_pDocument )
2169 priv->m_pDocument->pClass->destroy( priv->m_pDocument );
2170 priv->m_pDocument = nullptr;
2173 priv->m_pOffice->pClass->registerCallback(priv->m_pOffice, globalCallbackWorker, pDocView);
2174 priv->m_pDocument = priv->m_pOffice->pClass->documentLoad( priv->m_pOffice, priv->m_aDocPath.c_str() );
2175 if ( !priv->m_pDocument )
2177 char *pError = priv->m_pOffice->pClass->getError( priv->m_pOffice );
2178 g_task_return_new_error(task, g_quark_from_static_string ("LOK error"), 0, "%s", pError);
2180 else
2182 priv->m_eDocumentType = static_cast<LibreOfficeKitDocumentType>(priv->m_pDocument->pClass->getDocumentType(priv->m_pDocument));
2183 gdk_threads_add_idle(postDocumentLoad, pDocView);
2184 g_task_return_boolean (task, true);
2188 static void
2189 setPartInThread(gpointer data)
2191 GTask* task = G_TASK(data);
2192 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2193 LOKDocViewPrivate& priv = getPrivate(pDocView);
2194 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2195 int nPart = pLOEvent->m_nPart;
2197 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2198 std::stringstream ss;
2199 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2200 g_info("%s", ss.str().c_str());
2201 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2202 priv->m_pDocument->pClass->setPart( priv->m_pDocument, nPart );
2203 aGuard.unlock();
2205 lok_doc_view_reset_view(pDocView);
2208 static void
2209 setPartmodeInThread(gpointer data)
2211 GTask* task = G_TASK(data);
2212 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2213 LOKDocViewPrivate& priv = getPrivate(pDocView);
2214 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2215 int nPartMode = pLOEvent->m_nPartMode;
2217 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2218 std::stringstream ss;
2219 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2220 g_info("%s", ss.str().c_str());
2221 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2222 priv->m_pDocument->pClass->setPartMode( priv->m_pDocument, nPartMode );
2225 static void
2226 setEditInThread(gpointer data)
2228 GTask* task = G_TASK(data);
2229 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2230 LOKDocViewPrivate& priv = getPrivate(pDocView);
2231 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2232 gboolean bWasEdit = priv->m_bEdit;
2233 gboolean bEdit = pLOEvent->m_bEdit;
2235 if (!priv->m_bEdit && bEdit)
2236 g_info("lok_doc_view_set_edit: entering edit mode");
2237 else if (priv->m_bEdit && !bEdit)
2239 g_info("lok_doc_view_set_edit: leaving edit mode");
2240 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2241 std::stringstream ss;
2242 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2243 g_info("%s", ss.str().c_str());
2244 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2245 priv->m_pDocument->pClass->resetSelection(priv->m_pDocument);
2247 priv->m_bEdit = bEdit;
2248 g_signal_emit(pDocView, doc_view_signals[EDIT_CHANGED], 0, bWasEdit);
2249 gdk_threads_add_idle(queueDraw, GTK_WIDGET(pDocView));
2252 static void
2253 postCommandInThread (gpointer data)
2255 GTask* task = G_TASK(data);
2256 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2257 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2258 LOKDocViewPrivate& priv = getPrivate(pDocView);
2260 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2261 std::stringstream ss;
2262 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2263 g_info("%s", ss.str().c_str());
2264 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2265 ss.str(std::string());
2266 ss << "lok::Document::postUnoCommand(" << pLOEvent->m_pCommand << ", " << pLOEvent->m_pArguments << ")";
2267 g_info("%s", ss.str().c_str());
2268 priv->m_pDocument->pClass->postUnoCommand(priv->m_pDocument, pLOEvent->m_pCommand, pLOEvent->m_pArguments, pLOEvent->m_bNotifyWhenFinished);
2271 static void
2272 paintTileInThread (gpointer data)
2274 GTask* task = G_TASK(data);
2275 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2276 LOKDocViewPrivate& priv = getPrivate(pDocView);
2277 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2279 // check if "source" tile buffer is different from "current" tile buffer
2280 if (pLOEvent->m_pTileBuffer != &*priv->m_pTileBuffer)
2282 pLOEvent->m_pTileBuffer = nullptr;
2283 g_task_return_new_error(task,
2284 LOK_TILEBUFFER_ERROR,
2285 LOK_TILEBUFFER_CHANGED,
2286 "TileBuffer has changed");
2287 return;
2289 std::unique_ptr<TileBuffer>& buffer = priv->m_pTileBuffer;
2290 int index = pLOEvent->m_nPaintTileX * buffer->m_nWidth + pLOEvent->m_nPaintTileY;
2291 if (buffer->m_mTiles.find(index) != buffer->m_mTiles.end() &&
2292 buffer->m_mTiles[index].valid)
2293 return;
2295 cairo_surface_t *pSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nTileSizePixels, nTileSizePixels);
2296 if (cairo_surface_status(pSurface) != CAIRO_STATUS_SUCCESS)
2298 cairo_surface_destroy(pSurface);
2299 g_task_return_new_error(task,
2300 LOK_TILEBUFFER_ERROR,
2301 LOK_TILEBUFFER_MEMORY,
2302 "Error allocating Surface");
2303 return;
2306 unsigned char* pBuffer = cairo_image_surface_get_data(pSurface);
2307 GdkRectangle aTileRectangle;
2308 aTileRectangle.x = pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom) * pLOEvent->m_nPaintTileY;
2309 aTileRectangle.y = pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom) * pLOEvent->m_nPaintTileX;
2311 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2312 std::stringstream ss;
2313 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2314 g_info("%s", ss.str().c_str());
2315 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2316 ss.str(std::string());
2317 GTimer* aTimer = g_timer_new();
2318 gulong nElapsedMs;
2319 ss << "lok::Document::paintTile(" << static_cast<void*>(pBuffer) << ", "
2320 << nTileSizePixels << ", " << nTileSizePixels << ", "
2321 << aTileRectangle.x << ", " << aTileRectangle.y << ", "
2322 << pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom) << ", "
2323 << pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom) << ")";
2325 priv->m_pDocument->pClass->paintTile(priv->m_pDocument,
2326 pBuffer,
2327 nTileSizePixels, nTileSizePixels,
2328 aTileRectangle.x, aTileRectangle.y,
2329 pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom),
2330 pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom));
2331 aGuard.unlock();
2333 g_timer_elapsed(aTimer, &nElapsedMs);
2334 ss << " rendered in " << (nElapsedMs / 1000.) << " milliseconds";
2335 g_info("%s", ss.str().c_str());
2336 g_timer_destroy(aTimer);
2338 cairo_surface_mark_dirty(pSurface);
2340 // Its likely that while the tilebuffer has changed, one of the paint tile
2341 // requests has passed the previous check at start of this function, and has
2342 // rendered the tile already. We want to stop such rendered tiles from being
2343 // stored in new tile buffer.
2344 if (pLOEvent->m_pTileBuffer != &*priv->m_pTileBuffer)
2346 pLOEvent->m_pTileBuffer = nullptr;
2347 g_task_return_new_error(task,
2348 LOK_TILEBUFFER_ERROR,
2349 LOK_TILEBUFFER_CHANGED,
2350 "TileBuffer has changed");
2351 return;
2354 g_task_return_pointer(task, pSurface, reinterpret_cast<GDestroyNotify>(cairo_surface_destroy));
2358 static void
2359 lokThreadFunc(gpointer data, gpointer /*user_data*/)
2361 GTask* task = G_TASK(data);
2362 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2363 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2364 LOKDocViewPrivate& priv = getPrivate(pDocView);
2366 switch (pLOEvent->m_nType)
2368 case LOK_LOAD_DOC:
2369 openDocumentInThread(task);
2370 break;
2371 case LOK_POST_COMMAND:
2372 postCommandInThread(task);
2373 break;
2374 case LOK_SET_EDIT:
2375 setEditInThread(task);
2376 break;
2377 case LOK_SET_PART:
2378 setPartInThread(task);
2379 break;
2380 case LOK_SET_PARTMODE:
2381 setPartmodeInThread(task);
2382 break;
2383 case LOK_POST_KEY:
2384 // view-only/editable mode already checked during signal key signal emission
2385 postKeyEventInThread(task);
2386 break;
2387 case LOK_PAINT_TILE:
2388 paintTileInThread(task);
2389 break;
2390 case LOK_POST_MOUSE_EVENT:
2391 postMouseEventInThread(task);
2392 break;
2393 case LOK_SET_GRAPHIC_SELECTION:
2394 if (priv->m_bEdit)
2395 setGraphicSelectionInThread(task);
2396 else
2397 g_info ("LOK_SET_GRAPHIC_SELECTION: skipping graphical operation in view-only mode");
2398 break;
2399 case LOK_SET_CLIENT_ZOOM:
2400 setClientZoomInThread(task);
2401 break;
2404 g_object_unref(task);
2407 static void lok_doc_view_init (LOKDocView* pDocView)
2409 LOKDocViewPrivate& priv = getPrivate(pDocView);
2410 priv.m_pImpl = new LOKDocViewPrivateImpl();
2412 gtk_widget_add_events(GTK_WIDGET(pDocView),
2413 GDK_BUTTON_PRESS_MASK
2414 |GDK_BUTTON_RELEASE_MASK
2415 |GDK_BUTTON_MOTION_MASK
2416 |GDK_KEY_PRESS_MASK
2417 |GDK_KEY_RELEASE_MASK);
2419 priv->lokThreadPool = g_thread_pool_new(lokThreadFunc,
2420 nullptr,
2422 FALSE,
2423 nullptr);
2426 static void lok_doc_view_set_property (GObject* object, guint propId, const GValue *value, GParamSpec *pspec)
2428 LOKDocView* pDocView = LOK_DOC_VIEW (object);
2429 LOKDocViewPrivate& priv = getPrivate(pDocView);
2430 gboolean bDocPasswordEnabled = priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD;
2431 gboolean bDocPasswordToModifyEnabled = priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY;
2432 gboolean bTiledAnnotationsEnabled = !(priv->m_nLOKFeatures & LOK_FEATURE_NO_TILED_ANNOTATIONS);
2434 switch (propId)
2436 case PROP_LO_PATH:
2437 priv->m_aLOPath = g_value_get_string (value);
2438 break;
2439 case PROP_LO_UNIPOLL:
2440 priv->m_bUnipoll = g_value_get_boolean (value);
2441 break;
2442 case PROP_LO_POINTER:
2443 priv->m_pOffice = static_cast<LibreOfficeKit*>(g_value_get_pointer(value));
2444 break;
2445 case PROP_USER_PROFILE_URL:
2446 if (const gchar* pUserProfile = g_value_get_string(value))
2447 priv->m_aUserProfileURL = pUserProfile;
2448 break;
2449 case PROP_DOC_PATH:
2450 priv->m_aDocPath = g_value_get_string (value);
2451 break;
2452 case PROP_DOC_POINTER:
2453 priv->m_pDocument = static_cast<LibreOfficeKitDocument*>(g_value_get_pointer(value));
2454 priv->m_eDocumentType = static_cast<LibreOfficeKitDocumentType>(priv->m_pDocument->pClass->getDocumentType(priv->m_pDocument));
2455 break;
2456 case PROP_EDITABLE:
2457 lok_doc_view_set_edit (pDocView, g_value_get_boolean (value));
2458 break;
2459 case PROP_ZOOM:
2460 lok_doc_view_set_zoom (pDocView, g_value_get_float (value));
2461 break;
2462 case PROP_DOC_WIDTH:
2463 priv->m_nDocumentWidthTwips = g_value_get_long (value);
2464 break;
2465 case PROP_DOC_HEIGHT:
2466 priv->m_nDocumentHeightTwips = g_value_get_long (value);
2467 break;
2468 case PROP_DOC_PASSWORD:
2469 if (g_value_get_boolean (value) != bDocPasswordEnabled)
2471 priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_DOCUMENT_PASSWORD;
2472 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2474 break;
2475 case PROP_DOC_PASSWORD_TO_MODIFY:
2476 if ( g_value_get_boolean (value) != bDocPasswordToModifyEnabled)
2478 priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY;
2479 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2481 break;
2482 case PROP_TILED_ANNOTATIONS:
2483 if ( g_value_get_boolean (value) != bTiledAnnotationsEnabled)
2485 priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_NO_TILED_ANNOTATIONS;
2486 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2488 break;
2489 default:
2490 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec);
2494 static void lok_doc_view_get_property (GObject* object, guint propId, GValue *value, GParamSpec *pspec)
2496 LOKDocView* pDocView = LOK_DOC_VIEW (object);
2497 LOKDocViewPrivate& priv = getPrivate(pDocView);
2499 switch (propId)
2501 case PROP_LO_PATH:
2502 g_value_set_string (value, priv->m_aLOPath.c_str());
2503 break;
2504 case PROP_LO_UNIPOLL:
2505 g_value_set_boolean (value, priv->m_bUnipoll);
2506 break;
2507 case PROP_LO_POINTER:
2508 g_value_set_pointer(value, priv->m_pOffice);
2509 break;
2510 case PROP_USER_PROFILE_URL:
2511 g_value_set_string(value, priv->m_aUserProfileURL.c_str());
2512 break;
2513 case PROP_DOC_PATH:
2514 g_value_set_string (value, priv->m_aDocPath.c_str());
2515 break;
2516 case PROP_DOC_POINTER:
2517 g_value_set_pointer(value, priv->m_pDocument);
2518 break;
2519 case PROP_EDITABLE:
2520 g_value_set_boolean (value, priv->m_bEdit);
2521 break;
2522 case PROP_LOAD_PROGRESS:
2523 g_value_set_double (value, priv->m_nLoadProgress);
2524 break;
2525 case PROP_ZOOM:
2526 g_value_set_float (value, priv->m_fZoom);
2527 break;
2528 case PROP_IS_LOADING:
2529 g_value_set_boolean (value, priv->m_bIsLoading);
2530 break;
2531 case PROP_IS_INITIALIZED:
2532 g_value_set_boolean (value, priv->m_bInit);
2533 break;
2534 case PROP_DOC_WIDTH:
2535 g_value_set_long (value, priv->m_nDocumentWidthTwips);
2536 break;
2537 case PROP_DOC_HEIGHT:
2538 g_value_set_long (value, priv->m_nDocumentHeightTwips);
2539 break;
2540 case PROP_CAN_ZOOM_IN:
2541 g_value_set_boolean (value, priv->m_bCanZoomIn);
2542 break;
2543 case PROP_CAN_ZOOM_OUT:
2544 g_value_set_boolean (value, priv->m_bCanZoomOut);
2545 break;
2546 case PROP_DOC_PASSWORD:
2547 g_value_set_boolean (value, priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD);
2548 break;
2549 case PROP_DOC_PASSWORD_TO_MODIFY:
2550 g_value_set_boolean (value, priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY);
2551 break;
2552 case PROP_TILED_ANNOTATIONS:
2553 g_value_set_boolean (value, !(priv->m_nLOKFeatures & LOK_FEATURE_NO_TILED_ANNOTATIONS));
2554 break;
2555 default:
2556 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec);
2560 static gboolean lok_doc_view_draw (GtkWidget* pWidget, cairo_t* pCairo)
2562 LOKDocView *pDocView = LOK_DOC_VIEW (pWidget);
2564 renderDocument (pDocView, pCairo);
2565 renderOverlay (pDocView, pCairo);
2567 return FALSE;
2570 //rhbz#1444437 finalize may not occur immediately when this widget is destroyed
2571 //it may happen during GC of javascript, e.g. in gnome-documents but "destroy"
2572 //will be called promptly, so close documents in destroy, not finalize
2573 static void lok_doc_view_destroy (GtkWidget* widget)
2575 LOKDocView* pDocView = LOK_DOC_VIEW (widget);
2576 LOKDocViewPrivate& priv = getPrivate(pDocView);
2578 // Ignore notifications sent to this view on shutdown.
2579 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2580 std::stringstream ss;
2581 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2582 g_info("%s", ss.str().c_str());
2583 if (priv->m_pDocument)
2585 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2586 priv->m_pDocument->pClass->registerCallback(priv->m_pDocument, nullptr, nullptr);
2589 if (priv->lokThreadPool)
2591 g_thread_pool_free(priv->lokThreadPool, true, true);
2592 priv->lokThreadPool = nullptr;
2595 aGuard.unlock();
2597 if (priv->m_pDocument)
2599 if (priv->m_pDocument->pClass->getViewsCount(priv->m_pDocument) > 1)
2601 priv->m_pDocument->pClass->destroyView(priv->m_pDocument, priv->m_nViewId);
2603 else
2605 if (priv->m_pDocument)
2607 priv->m_pDocument->pClass->destroy (priv->m_pDocument);
2608 priv->m_pDocument = nullptr;
2610 if (priv->m_pOffice)
2612 priv->m_pOffice->pClass->destroy (priv->m_pOffice);
2613 priv->m_pOffice = nullptr;
2618 GTK_WIDGET_CLASS (lok_doc_view_parent_class)->destroy (widget);
2621 static void lok_doc_view_finalize (GObject* object)
2623 LOKDocView* pDocView = LOK_DOC_VIEW (object);
2624 LOKDocViewPrivate& priv = getPrivate(pDocView);
2626 delete priv.m_pImpl;
2627 priv.m_pImpl = nullptr;
2629 G_OBJECT_CLASS (lok_doc_view_parent_class)->finalize (object);
2632 // kicks the mainloop awake
2633 static gboolean timeout_wakeup(void *)
2635 return FALSE;
2638 // integrate our mainloop with LOK's
2639 static int lok_poll_callback(void*, int timeoutUs)
2641 if (timeoutUs)
2643 guint timeout = g_timeout_add(timeoutUs / 1000, timeout_wakeup, nullptr);
2644 g_main_context_iteration(nullptr, TRUE);
2645 g_source_remove(timeout);
2647 else
2648 g_main_context_iteration(nullptr, FALSE);
2650 return 0;
2653 // thread-safe wakeup of our mainloop
2654 static void lok_wake_callback(void *)
2656 g_main_context_wakeup(nullptr);
2659 static gboolean spin_lok_loop(void *pData)
2661 LOKDocView *pDocView = LOK_DOC_VIEW (pData);
2662 LOKDocViewPrivate& priv = getPrivate(pDocView);
2663 priv->m_pOffice->pClass->runLoop(priv->m_pOffice, lok_poll_callback, lok_wake_callback, nullptr);
2664 return FALSE;
2667 static gboolean lok_doc_view_initable_init (GInitable *initable, GCancellable* /*cancellable*/, GError **error)
2669 LOKDocView *pDocView = LOK_DOC_VIEW (initable);
2670 LOKDocViewPrivate& priv = getPrivate(pDocView);
2672 if (priv->m_pOffice != nullptr)
2673 return TRUE;
2675 if (priv->m_bUnipoll)
2676 g_setenv("SAL_LOK_OPTIONS", "unipoll", FALSE);
2678 priv->m_pOffice = lok_init_2(priv->m_aLOPath.c_str(), priv->m_aUserProfileURL.empty() ? nullptr : priv->m_aUserProfileURL.c_str());
2680 if (priv->m_pOffice == nullptr)
2682 g_set_error (error,
2683 g_quark_from_static_string ("LOK initialization error"), 0,
2684 "Failed to get LibreOfficeKit context. Make sure path (%s) is correct",
2685 priv->m_aLOPath.c_str());
2686 return FALSE;
2688 priv->m_nLOKFeatures |= LOK_FEATURE_PART_IN_INVALIDATION_CALLBACK;
2689 priv->m_nLOKFeatures |= LOK_FEATURE_VIEWID_IN_VISCURSOR_INVALIDATION_CALLBACK;
2690 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2692 if (priv->m_bUnipoll)
2693 g_idle_add(spin_lok_loop, pDocView);
2695 return TRUE;
2698 static void lok_doc_view_initable_iface_init (GInitableIface *iface)
2700 iface->init = lok_doc_view_initable_init;
2703 static void lok_doc_view_class_init (LOKDocViewClass* pClass)
2705 GObjectClass *pGObjectClass = G_OBJECT_CLASS(pClass);
2706 GtkWidgetClass *pWidgetClass = GTK_WIDGET_CLASS(pClass);
2708 pGObjectClass->get_property = lok_doc_view_get_property;
2709 pGObjectClass->set_property = lok_doc_view_set_property;
2710 pGObjectClass->finalize = lok_doc_view_finalize;
2712 pWidgetClass->draw = lok_doc_view_draw;
2713 pWidgetClass->button_press_event = lok_doc_view_signal_button;
2714 pWidgetClass->button_release_event = lok_doc_view_signal_button;
2715 pWidgetClass->key_press_event = signalKey;
2716 pWidgetClass->key_release_event = signalKey;
2717 pWidgetClass->motion_notify_event = lok_doc_view_signal_motion;
2718 pWidgetClass->destroy = lok_doc_view_destroy;
2721 * LOKDocView:lopath:
2723 * The absolute path of the LibreOffice install.
2725 properties[PROP_LO_PATH] =
2726 g_param_spec_string("lopath",
2727 "LO Path",
2728 "LibreOffice Install Path",
2729 nullptr,
2730 static_cast<GParamFlags>(G_PARAM_READWRITE |
2731 G_PARAM_CONSTRUCT_ONLY |
2732 G_PARAM_STATIC_STRINGS));
2735 * LOKDocView:unipoll:
2737 * Whether we use our own unified polling mainloop in place of glib's
2739 properties[PROP_LO_UNIPOLL] =
2740 g_param_spec_boolean("unipoll",
2741 "Unified Polling",
2742 "Whether we use a custom unified polling loop",
2743 FALSE,
2744 static_cast<GParamFlags>(G_PARAM_READWRITE |
2745 G_PARAM_CONSTRUCT_ONLY |
2746 G_PARAM_STATIC_STRINGS));
2748 * LOKDocView:lopointer:
2750 * A LibreOfficeKit* in case lok_init() is already called
2751 * previously.
2753 properties[PROP_LO_POINTER] =
2754 g_param_spec_pointer("lopointer",
2755 "LO Pointer",
2756 "A LibreOfficeKit* from lok_init()",
2757 static_cast<GParamFlags>(G_PARAM_READWRITE |
2758 G_PARAM_CONSTRUCT_ONLY |
2759 G_PARAM_STATIC_STRINGS));
2762 * LOKDocView:userprofileurl:
2764 * The absolute path of the LibreOffice user profile.
2766 properties[PROP_USER_PROFILE_URL] =
2767 g_param_spec_string("userprofileurl",
2768 "User profile path",
2769 "LibreOffice user profile path",
2770 nullptr,
2771 static_cast<GParamFlags>(G_PARAM_READWRITE |
2772 G_PARAM_CONSTRUCT_ONLY |
2773 G_PARAM_STATIC_STRINGS));
2776 * LOKDocView:docpath:
2778 * The path of the document that is currently being viewed.
2780 properties[PROP_DOC_PATH] =
2781 g_param_spec_string("docpath",
2782 "Document Path",
2783 "The URI of the document to open",
2784 nullptr,
2785 static_cast<GParamFlags>(G_PARAM_READWRITE |
2786 G_PARAM_STATIC_STRINGS));
2789 * LOKDocView:docpointer:
2791 * A LibreOfficeKitDocument* in case documentLoad() is already called
2792 * previously.
2794 properties[PROP_DOC_POINTER] =
2795 g_param_spec_pointer("docpointer",
2796 "Document Pointer",
2797 "A LibreOfficeKitDocument* from documentLoad()",
2798 static_cast<GParamFlags>(G_PARAM_READWRITE |
2799 G_PARAM_STATIC_STRINGS));
2802 * LOKDocView:editable:
2804 * Whether the document loaded inside of #LOKDocView is editable or not.
2806 properties[PROP_EDITABLE] =
2807 g_param_spec_boolean("editable",
2808 "Editable",
2809 "Whether the content is in edit mode or not",
2810 FALSE,
2811 static_cast<GParamFlags>(G_PARAM_READWRITE |
2812 G_PARAM_STATIC_STRINGS));
2815 * LOKDocView:load-progress:
2817 * The percent completion of the current loading operation of the
2818 * document. This can be used for progress bars. Note that this is not a
2819 * very accurate progress indicator, and its value might reset it couple of
2820 * times to 0 and start again. You should not rely on its numbers.
2822 properties[PROP_LOAD_PROGRESS] =
2823 g_param_spec_double("load-progress",
2824 "Estimated Load Progress",
2825 "Shows the progress of the document load operation",
2826 0.0, 1.0, 0.0,
2827 static_cast<GParamFlags>(G_PARAM_READABLE |
2828 G_PARAM_STATIC_STRINGS));
2831 * LOKDocView:zoom-level:
2833 * The current zoom level of the document loaded inside #LOKDocView. The
2834 * default value is 1.0.
2836 properties[PROP_ZOOM] =
2837 g_param_spec_float("zoom-level",
2838 "Zoom Level",
2839 "The current zoom level of the content",
2840 0, 5.0, 1.0,
2841 static_cast<GParamFlags>(G_PARAM_READWRITE |
2842 G_PARAM_STATIC_STRINGS));
2845 * LOKDocView:is-loading:
2847 * Whether the requested document is being loaded or not. %TRUE if it is
2848 * being loaded, otherwise %FALSE.
2850 properties[PROP_IS_LOADING] =
2851 g_param_spec_boolean("is-loading",
2852 "Is Loading",
2853 "Whether the view is loading a document",
2854 FALSE,
2855 static_cast<GParamFlags>(G_PARAM_READABLE |
2856 G_PARAM_STATIC_STRINGS));
2859 * LOKDocView:is-initialized:
2861 * Whether the requested document has completely loaded or not.
2863 properties[PROP_IS_INITIALIZED] =
2864 g_param_spec_boolean("is-initialized",
2865 "Has initialized",
2866 "Whether the view has completely initialized",
2867 FALSE,
2868 static_cast<GParamFlags>(G_PARAM_READABLE |
2869 G_PARAM_STATIC_STRINGS));
2872 * LOKDocView:doc-width:
2874 * The width of the currently loaded document in #LOKDocView in twips.
2876 properties[PROP_DOC_WIDTH] =
2877 g_param_spec_long("doc-width",
2878 "Document Width",
2879 "Width of the document in twips",
2880 0, G_MAXLONG, 0,
2881 static_cast<GParamFlags>(G_PARAM_READWRITE |
2882 G_PARAM_STATIC_STRINGS));
2885 * LOKDocView:doc-height:
2887 * The height of the currently loaded document in #LOKDocView in twips.
2889 properties[PROP_DOC_HEIGHT] =
2890 g_param_spec_long("doc-height",
2891 "Document Height",
2892 "Height of the document in twips",
2893 0, G_MAXLONG, 0,
2894 static_cast<GParamFlags>(G_PARAM_READWRITE |
2895 G_PARAM_STATIC_STRINGS));
2898 * LOKDocView:can-zoom-in:
2900 * It tells whether the view can further be zoomed in or not.
2902 properties[PROP_CAN_ZOOM_IN] =
2903 g_param_spec_boolean("can-zoom-in",
2904 "Can Zoom In",
2905 "Whether the view can be zoomed in further",
2906 TRUE,
2907 static_cast<GParamFlags>(G_PARAM_READABLE
2908 | G_PARAM_STATIC_STRINGS));
2911 * LOKDocView:can-zoom-out:
2913 * It tells whether the view can further be zoomed out or not.
2915 properties[PROP_CAN_ZOOM_OUT] =
2916 g_param_spec_boolean("can-zoom-out",
2917 "Can Zoom Out",
2918 "Whether the view can be zoomed out further",
2919 TRUE,
2920 static_cast<GParamFlags>(G_PARAM_READABLE
2921 | G_PARAM_STATIC_STRINGS));
2924 * LOKDocView:doc-password:
2926 * Set it to true if client supports providing password for viewing
2927 * password protected documents
2929 properties[PROP_DOC_PASSWORD] =
2930 g_param_spec_boolean("doc-password",
2931 "Document password capability",
2932 "Whether client supports providing document passwords",
2933 FALSE,
2934 static_cast<GParamFlags>(G_PARAM_READWRITE
2935 | G_PARAM_STATIC_STRINGS));
2938 * LOKDocView:doc-password-to-modify:
2940 * Set it to true if client supports providing password for edit-protected documents
2942 properties[PROP_DOC_PASSWORD_TO_MODIFY] =
2943 g_param_spec_boolean("doc-password-to-modify",
2944 "Edit document password capability",
2945 "Whether the client supports providing passwords to edit documents",
2946 FALSE,
2947 static_cast<GParamFlags>(G_PARAM_READWRITE
2948 | G_PARAM_STATIC_STRINGS));
2951 * LOKDocView:tiled-annotations-rendering:
2953 * Set it to false if client does not want LO to render comments in tiles and
2954 * instead interested in using comments API to access comments
2956 properties[PROP_TILED_ANNOTATIONS] =
2957 g_param_spec_boolean("tiled-annotations",
2958 "Render comments in tiles",
2959 "Whether the client wants in tile comment rendering",
2960 TRUE,
2961 static_cast<GParamFlags>(G_PARAM_READWRITE
2962 | G_PARAM_STATIC_STRINGS));
2964 g_object_class_install_properties(pGObjectClass, PROP_LAST, properties);
2967 * LOKDocView::load-changed:
2968 * @pDocView: the #LOKDocView on which the signal is emitted
2969 * @fLoadProgress: the new progress value
2971 doc_view_signals[LOAD_CHANGED] =
2972 g_signal_new("load-changed",
2973 G_TYPE_FROM_CLASS (pGObjectClass),
2974 G_SIGNAL_RUN_FIRST,
2976 nullptr, nullptr,
2977 g_cclosure_marshal_VOID__DOUBLE,
2978 G_TYPE_NONE, 1,
2979 G_TYPE_DOUBLE);
2982 * LOKDocView::edit-changed:
2983 * @pDocView: the #LOKDocView on which the signal is emitted
2984 * @bEdit: the new edit value of the view
2986 doc_view_signals[EDIT_CHANGED] =
2987 g_signal_new("edit-changed",
2988 G_TYPE_FROM_CLASS (pGObjectClass),
2989 G_SIGNAL_RUN_FIRST,
2991 nullptr, nullptr,
2992 g_cclosure_marshal_VOID__BOOLEAN,
2993 G_TYPE_NONE, 1,
2994 G_TYPE_BOOLEAN);
2997 * LOKDocView::command-changed:
2998 * @pDocView: the #LOKDocView on which the signal is emitted
2999 * @aCommand: the command that was changed
3001 doc_view_signals[COMMAND_CHANGED] =
3002 g_signal_new("command-changed",
3003 G_TYPE_FROM_CLASS(pGObjectClass),
3004 G_SIGNAL_RUN_FIRST,
3006 nullptr, nullptr,
3007 g_cclosure_marshal_VOID__STRING,
3008 G_TYPE_NONE, 1,
3009 G_TYPE_STRING);
3012 * LOKDocView::search-not-found:
3013 * @pDocView: the #LOKDocView on which the signal is emitted
3014 * @aCommand: the string for which the search was not found.
3016 doc_view_signals[SEARCH_NOT_FOUND] =
3017 g_signal_new("search-not-found",
3018 G_TYPE_FROM_CLASS(pGObjectClass),
3019 G_SIGNAL_RUN_FIRST,
3021 nullptr, nullptr,
3022 g_cclosure_marshal_VOID__STRING,
3023 G_TYPE_NONE, 1,
3024 G_TYPE_STRING);
3027 * LOKDocView::part-changed:
3028 * @pDocView: the #LOKDocView on which the signal is emitted
3029 * @aCommand: the part number which the view changed to
3031 doc_view_signals[PART_CHANGED] =
3032 g_signal_new("part-changed",
3033 G_TYPE_FROM_CLASS(pGObjectClass),
3034 G_SIGNAL_RUN_FIRST,
3036 nullptr, nullptr,
3037 g_cclosure_marshal_VOID__INT,
3038 G_TYPE_NONE, 1,
3039 G_TYPE_INT);
3042 * LOKDocView::size-changed:
3043 * @pDocView: the #LOKDocView on which the signal is emitted
3044 * @aCommand: NULL, we just notify that want to notify the UI elements that are interested.
3046 doc_view_signals[SIZE_CHANGED] =
3047 g_signal_new("size-changed",
3048 G_TYPE_FROM_CLASS(pGObjectClass),
3049 G_SIGNAL_RUN_FIRST,
3051 nullptr, nullptr,
3052 g_cclosure_marshal_VOID__VOID,
3053 G_TYPE_NONE, 1,
3054 G_TYPE_INT);
3057 * LOKDocView::hyperlinked-clicked:
3058 * @pDocView: the #LOKDocView on which the signal is emitted
3059 * @aHyperlink: the URI which the application should handle
3061 doc_view_signals[HYPERLINK_CLICKED] =
3062 g_signal_new("hyperlink-clicked",
3063 G_TYPE_FROM_CLASS(pGObjectClass),
3064 G_SIGNAL_RUN_FIRST,
3066 nullptr, nullptr,
3067 g_cclosure_marshal_VOID__STRING,
3068 G_TYPE_NONE, 1,
3069 G_TYPE_STRING);
3072 * LOKDocView::cursor-changed:
3073 * @pDocView: the #LOKDocView on which the signal is emitted
3074 * @nX: The new cursor position (X coordinate) in pixels
3075 * @nY: The new cursor position (Y coordinate) in pixels
3076 * @nWidth: The width of new cursor
3077 * @nHeight: The height of new cursor
3079 doc_view_signals[CURSOR_CHANGED] =
3080 g_signal_new("cursor-changed",
3081 G_TYPE_FROM_CLASS(pGObjectClass),
3082 G_SIGNAL_RUN_FIRST,
3084 nullptr, nullptr,
3085 g_cclosure_marshal_generic,
3086 G_TYPE_NONE, 4,
3087 G_TYPE_INT, G_TYPE_INT,
3088 G_TYPE_INT, G_TYPE_INT);
3091 * LOKDocView::search-result-count:
3092 * @pDocView: the #LOKDocView on which the signal is emitted
3093 * @aCommand: number of matches.
3095 doc_view_signals[SEARCH_RESULT_COUNT] =
3096 g_signal_new("search-result-count",
3097 G_TYPE_FROM_CLASS(pGObjectClass),
3098 G_SIGNAL_RUN_FIRST,
3100 nullptr, nullptr,
3101 g_cclosure_marshal_VOID__STRING,
3102 G_TYPE_NONE, 1,
3103 G_TYPE_STRING);
3106 * LOKDocView::command-result:
3107 * @pDocView: the #LOKDocView on which the signal is emitted
3108 * @aCommand: JSON containing the info about the command that finished,
3109 * and its success status.
3111 doc_view_signals[COMMAND_RESULT] =
3112 g_signal_new("command-result",
3113 G_TYPE_FROM_CLASS(pGObjectClass),
3114 G_SIGNAL_RUN_FIRST,
3116 nullptr, nullptr,
3117 g_cclosure_marshal_VOID__STRING,
3118 G_TYPE_NONE, 1,
3119 G_TYPE_STRING);
3122 * LOKDocView::address-changed:
3123 * @pDocView: the #LOKDocView on which the signal is emitted
3124 * @aCommand: formula text content
3126 doc_view_signals[ADDRESS_CHANGED] =
3127 g_signal_new("address-changed",
3128 G_TYPE_FROM_CLASS(pGObjectClass),
3129 G_SIGNAL_RUN_FIRST,
3131 nullptr, nullptr,
3132 g_cclosure_marshal_VOID__STRING,
3133 G_TYPE_NONE, 1,
3134 G_TYPE_STRING);
3137 * LOKDocView::formula-changed:
3138 * @pDocView: the #LOKDocView on which the signal is emitted
3139 * @aCommand: formula text content
3141 doc_view_signals[FORMULA_CHANGED] =
3142 g_signal_new("formula-changed",
3143 G_TYPE_FROM_CLASS(pGObjectClass),
3144 G_SIGNAL_RUN_FIRST,
3146 nullptr, nullptr,
3147 g_cclosure_marshal_VOID__STRING,
3148 G_TYPE_NONE, 1,
3149 G_TYPE_STRING);
3152 * LOKDocView::text-selection:
3153 * @pDocView: the #LOKDocView on which the signal is emitted
3154 * @bIsTextSelected: whether text selected is non-null
3156 doc_view_signals[TEXT_SELECTION] =
3157 g_signal_new("text-selection",
3158 G_TYPE_FROM_CLASS(pGObjectClass),
3159 G_SIGNAL_RUN_FIRST,
3161 nullptr, nullptr,
3162 g_cclosure_marshal_VOID__BOOLEAN,
3163 G_TYPE_NONE, 1,
3164 G_TYPE_BOOLEAN);
3167 * LOKDocView::password-required:
3168 * @pDocView: the #LOKDocView on which the signal is emitted
3169 * @pUrl: URL of the document for which password is required
3170 * @bModify: whether password id required to modify the document
3171 * This is true when password is required to edit the document,
3172 * while it can still be viewed without password. In such cases, provide a NULL
3173 * password for read-only access to the document.
3174 * If false, password is required for opening the document, and document
3175 * cannot be opened without providing a valid password.
3177 * Password must be provided by calling lok_doc_view_set_document_password
3178 * function with pUrl as provided by the callback.
3180 * Upon entering an invalid password, another `password-required` signal is
3181 * emitted.
3182 * Upon entering a valid password, document starts to load.
3183 * Upon entering a NULL password: if bModify is %TRUE, document starts to
3184 * open in view-only mode, else loading of document is aborted.
3186 doc_view_signals[PASSWORD_REQUIRED] =
3187 g_signal_new("password-required",
3188 G_TYPE_FROM_CLASS(pGObjectClass),
3189 G_SIGNAL_RUN_FIRST,
3191 nullptr, nullptr,
3192 g_cclosure_marshal_generic,
3193 G_TYPE_NONE, 2,
3194 G_TYPE_STRING,
3195 G_TYPE_BOOLEAN);
3198 * LOKDocView::comment:
3199 * @pDocView: the #LOKDocView on which the signal is emitted
3200 * @pComment: the JSON string containing comment notification
3201 * The has following structure containing the information telling whether
3202 * the comment has been added, deleted or modified.
3203 * The example:
3205 * "comment": {
3206 * "action": "Add",
3207 * "id": "11",
3208 * "parent": "4",
3209 * "author": "Unknown Author",
3210 * "text": "This is a comment",
3211 * "dateTime": "2016-08-18T13:13:00",
3212 * "anchorPos": "4529, 3906",
3213 * "textRange": "1418, 3906, 3111, 919"
3216 * 'action' can be 'Add', 'Remove' or 'Modify' depending on whether
3217 * comment has been added, removed or modified.
3218 * 'parent' is a non-zero comment id if this comment is a reply comment,
3219 * otherwise it's a root comment.
3221 doc_view_signals[COMMENT] =
3222 g_signal_new("comment",
3223 G_TYPE_FROM_CLASS(pGObjectClass),
3224 G_SIGNAL_RUN_FIRST,
3226 nullptr, nullptr,
3227 g_cclosure_marshal_generic,
3228 G_TYPE_NONE, 1,
3229 G_TYPE_STRING);
3232 * LOKDocView::ruler:
3233 * @pDocView: the #LOKDocView on which the signal is emitted
3234 * @pPayload: the JSON string containing the information about ruler properties
3236 * The payload format is:
3239 * "margin1": "...",
3240 * "margin2": "...",
3241 * "leftOffset": "...",
3242 * "pageOffset": "...",
3243 * "pageWidth": "...",
3244 * "unit": "..."
3247 doc_view_signals[RULER] =
3248 g_signal_new("ruler",
3249 G_TYPE_FROM_CLASS(pGObjectClass),
3250 G_SIGNAL_RUN_FIRST,
3252 nullptr, nullptr,
3253 g_cclosure_marshal_generic,
3254 G_TYPE_NONE, 1,
3255 G_TYPE_STRING);
3258 * LOKDocView::window::
3259 * @pDocView: the #LOKDocView on which the signal is emitted
3260 * @pPayload: the JSON string containing the information about the window
3262 * This signal emits information about external windows like dialogs, autopopups for now.
3264 * The payload format of pPayload is:
3267 * "id": "unique integer id of the dialog",
3268 * "action": "<see below>",
3269 * "type": "<see below>"
3270 * "rectangle": "x, y, width, height"
3273 * "type" tells the type of the window the action is associated with
3274 * - "dialog" - window is a dialog
3275 * - "child" - window is a floating window (combo boxes, etc.)
3277 * "action" can take following values:
3278 * - "created" - window is created in the backend, client can render it now
3279 * - "title_changed" - window's title is changed
3280 * - "size_changed" - window's size is changed
3281 * - "invalidate" - the area as described by "rectangle" is invalidated
3282 * Clients must request the new area
3283 * - "cursor_invalidate" - cursor is invalidated. New position is in "rectangle"
3284 * - "cursor_visible" - cursor visible status is changed. Status is available
3285 * in "visible" field
3286 * - "close" - window is closed
3288 doc_view_signals[WINDOW] =
3289 g_signal_new("window",
3290 G_TYPE_FROM_CLASS(pGObjectClass),
3291 G_SIGNAL_RUN_FIRST,
3293 nullptr, nullptr,
3294 g_cclosure_marshal_generic,
3295 G_TYPE_NONE, 1,
3296 G_TYPE_STRING);
3299 * LOKDocView::invalidate-header::
3300 * @pDocView: the #LOKDocView on which the signal is emitted
3301 * @pPayload: can be either "row", "column", or "all".
3303 * The column/row header is no more valid because of a column/row insertion
3304 * or a similar event. Clients must query a new column/row header set.
3306 * The payload says if we are invalidating a row or column header
3308 doc_view_signals[INVALIDATE_HEADER] =
3309 g_signal_new("invalidate-header",
3310 G_TYPE_FROM_CLASS(pGObjectClass),
3311 G_SIGNAL_RUN_FIRST,
3313 nullptr, nullptr,
3314 g_cclosure_marshal_generic,
3315 G_TYPE_NONE, 1,
3316 G_TYPE_STRING);
3319 SAL_DLLPUBLIC_EXPORT GtkWidget*
3320 lok_doc_view_new (const gchar* pPath, GCancellable *cancellable, GError **error)
3322 return GTK_WIDGET (g_initable_new (LOK_TYPE_DOC_VIEW, cancellable, error,
3323 "lopath", pPath == nullptr ? LOK_PATH : pPath,
3324 "halign", GTK_ALIGN_CENTER,
3325 "valign", GTK_ALIGN_CENTER,
3326 nullptr));
3329 SAL_DLLPUBLIC_EXPORT GtkWidget*
3330 lok_doc_view_new_from_user_profile (const gchar* pPath, const gchar* pUserProfile, GCancellable *cancellable, GError **error)
3332 return GTK_WIDGET(g_initable_new(LOK_TYPE_DOC_VIEW, cancellable, error,
3333 "lopath", pPath == nullptr ? LOK_PATH : pPath,
3334 "userprofileurl", pUserProfile,
3335 "halign", GTK_ALIGN_CENTER,
3336 "valign", GTK_ALIGN_CENTER,
3337 nullptr));
3340 SAL_DLLPUBLIC_EXPORT GtkWidget* lok_doc_view_new_from_widget(LOKDocView* pOldLOKDocView,
3341 const gchar* pRenderingArguments)
3343 LOKDocViewPrivate& pOldPriv = getPrivate(pOldLOKDocView);
3344 GtkWidget* pNewDocView = GTK_WIDGET(g_initable_new(LOK_TYPE_DOC_VIEW, /*cancellable=*/nullptr, /*error=*/nullptr,
3345 "lopath", pOldPriv->m_aLOPath.c_str(),
3346 "userprofileurl", pOldPriv->m_aUserProfileURL.c_str(),
3347 "lopointer", pOldPriv->m_pOffice,
3348 "docpointer", pOldPriv->m_pDocument,
3349 "halign", GTK_ALIGN_CENTER,
3350 "valign", GTK_ALIGN_CENTER,
3351 nullptr));
3353 // No documentLoad(), just a createView().
3354 LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(pNewDocView));
3355 LOKDocViewPrivate& pNewPriv = getPrivate(LOK_DOC_VIEW(pNewDocView));
3356 // Store the view id only later in postDocumentLoad(), as
3357 // initializeForRendering() changes the id in Impress.
3358 pDocument->pClass->createView(pDocument);
3359 pNewPriv->m_aRenderingArguments = pRenderingArguments;
3361 postDocumentLoad(pNewDocView);
3362 return pNewDocView;
3365 SAL_DLLPUBLIC_EXPORT gboolean
3366 lok_doc_view_open_document_finish (LOKDocView* pDocView, GAsyncResult* res, GError** error)
3368 GTask* task = G_TASK(res);
3370 g_return_val_if_fail(g_task_is_valid(res, pDocView), false);
3371 g_return_val_if_fail(g_task_get_source_tag(task) == lok_doc_view_open_document, false);
3372 g_return_val_if_fail(error == nullptr || *error == nullptr, false);
3374 return g_task_propagate_boolean(task, error);
3377 SAL_DLLPUBLIC_EXPORT void
3378 lok_doc_view_open_document (LOKDocView* pDocView,
3379 const gchar* pPath,
3380 const gchar* pRenderingArguments,
3381 GCancellable* cancellable,
3382 GAsyncReadyCallback callback,
3383 gpointer userdata)
3385 GTask* task = g_task_new(pDocView, cancellable, callback, userdata);
3386 LOKDocViewPrivate& priv = getPrivate(pDocView);
3387 GError* error = nullptr;
3389 LOEvent* pLOEvent = new LOEvent(LOK_LOAD_DOC);
3391 g_object_set(G_OBJECT(pDocView), "docpath", pPath, nullptr);
3392 if (pRenderingArguments)
3393 priv->m_aRenderingArguments = pRenderingArguments;
3394 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3395 g_task_set_source_tag(task, reinterpret_cast<gpointer>(lok_doc_view_open_document));
3397 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3398 if (error != nullptr)
3400 g_warning("Unable to call LOK_LOAD_DOC: %s", error->message);
3401 g_clear_error(&error);
3403 g_object_unref(task);
3406 SAL_DLLPUBLIC_EXPORT LibreOfficeKitDocument*
3407 lok_doc_view_get_document (LOKDocView* pDocView)
3409 LOKDocViewPrivate& priv = getPrivate(pDocView);
3410 return priv->m_pDocument;
3413 SAL_DLLPUBLIC_EXPORT void
3414 lok_doc_view_set_visible_area (LOKDocView* pDocView, GdkRectangle* pVisibleArea)
3416 if (!pVisibleArea)
3417 return;
3419 LOKDocViewPrivate& priv = getPrivate(pDocView);
3420 priv->m_aVisibleArea = *pVisibleArea;
3421 priv->m_bVisibleAreaSet = true;
3424 namespace {
3425 // This used to be rtl::math::approxEqual() but since that isn't inline anymore
3426 // in rtl/math.hxx and was moved into libuno_sal as rtl_math_approxEqual() to
3427 // cater for representable integer cases and we don't want to link against
3428 // libuno_sal, we'll have to have an own implementation. The special large
3429 // integer cases seems not be needed here.
3430 bool lok_approxEqual(double a, double b)
3432 static const double e48 = 1.0 / (16777216.0 * 16777216.0);
3433 if (a == b)
3434 return true;
3435 if (a == 0.0 || b == 0.0)
3436 return false;
3437 const double d = fabs(a - b);
3438 return (d < fabs(a) * e48 && d < fabs(b) * e48);
3442 SAL_DLLPUBLIC_EXPORT void
3443 lok_doc_view_set_zoom (LOKDocView* pDocView, float fZoom)
3445 LOKDocViewPrivate& priv = getPrivate(pDocView);
3446 GError* error = nullptr;
3448 if (!priv->m_pDocument)
3449 return;
3451 // Clamp the input value in [MIN_ZOOM, MAX_ZOOM]
3452 fZoom = fZoom < MIN_ZOOM ? MIN_ZOOM : fZoom;
3453 fZoom = std::min(fZoom, MAX_ZOOM);
3455 if (lok_approxEqual(fZoom, priv->m_fZoom))
3456 return;
3458 priv->m_fZoom = fZoom;
3459 long nDocumentWidthPixels = twipToPixel(priv->m_nDocumentWidthTwips, fZoom);
3460 long nDocumentHeightPixels = twipToPixel(priv->m_nDocumentHeightTwips, fZoom);
3461 // Total number of columns in this document.
3462 guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixels);
3464 priv->m_pTileBuffer = std::make_unique<TileBuffer>(nColumns);
3465 gtk_widget_set_size_request(GTK_WIDGET(pDocView),
3466 nDocumentWidthPixels,
3467 nDocumentHeightPixels);
3469 g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_ZOOM]);
3471 // set properties to indicate if view can be further zoomed in/out
3472 bool bCanZoomIn = priv->m_fZoom < MAX_ZOOM;
3473 bool bCanZoomOut = priv->m_fZoom > MIN_ZOOM;
3474 if (bCanZoomIn != bool(priv->m_bCanZoomIn))
3476 priv->m_bCanZoomIn = bCanZoomIn;
3477 g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_CAN_ZOOM_IN]);
3479 if (bCanZoomOut != bool(priv->m_bCanZoomOut))
3481 priv->m_bCanZoomOut = bCanZoomOut;
3482 g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_CAN_ZOOM_OUT]);
3485 // Update the client's view size
3486 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3487 LOEvent* pLOEvent = new LOEvent(LOK_SET_CLIENT_ZOOM);
3488 pLOEvent->m_nTilePixelWidth = nTileSizePixels;
3489 pLOEvent->m_nTilePixelHeight = nTileSizePixels;
3490 pLOEvent->m_nTileTwipWidth = pixelToTwip(nTileSizePixels, fZoom);
3491 pLOEvent->m_nTileTwipHeight = pixelToTwip(nTileSizePixels, fZoom);
3492 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3494 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3495 if (error != nullptr)
3497 g_warning("Unable to call LOK_SET_CLIENT_ZOOM: %s", error->message);
3498 g_clear_error(&error);
3500 g_object_unref(task);
3502 priv->m_nTileSizeTwips = pixelToTwip(nTileSizePixels, priv->m_fZoom);
3505 SAL_DLLPUBLIC_EXPORT gfloat
3506 lok_doc_view_get_zoom (LOKDocView* pDocView)
3508 LOKDocViewPrivate& priv = getPrivate(pDocView);
3509 return priv->m_fZoom;
3512 SAL_DLLPUBLIC_EXPORT gint
3513 lok_doc_view_get_parts (LOKDocView* pDocView)
3515 LOKDocViewPrivate& priv = getPrivate(pDocView);
3516 if (!priv->m_pDocument)
3517 return -1;
3519 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
3520 std::stringstream ss;
3521 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
3522 g_info("%s", ss.str().c_str());
3523 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
3524 return priv->m_pDocument->pClass->getParts( priv->m_pDocument );
3527 SAL_DLLPUBLIC_EXPORT gint
3528 lok_doc_view_get_part (LOKDocView* pDocView)
3530 LOKDocViewPrivate& priv = getPrivate(pDocView);
3531 if (!priv->m_pDocument)
3532 return -1;
3534 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
3535 std::stringstream ss;
3536 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
3537 g_info("%s", ss.str().c_str());
3538 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
3539 return priv->m_pDocument->pClass->getPart( priv->m_pDocument );
3542 SAL_DLLPUBLIC_EXPORT void
3543 lok_doc_view_set_part (LOKDocView* pDocView, int nPart)
3545 LOKDocViewPrivate& priv = getPrivate(pDocView);
3546 if (!priv->m_pDocument)
3547 return;
3549 if (nPart < 0 || nPart >= priv->m_nParts)
3551 g_warning("Invalid part request : %d", nPart);
3552 return;
3555 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3556 LOEvent* pLOEvent = new LOEvent(LOK_SET_PART);
3557 GError* error = nullptr;
3559 pLOEvent->m_nPart = nPart;
3560 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3562 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3563 if (error != nullptr)
3565 g_warning("Unable to call LOK_SET_PART: %s", error->message);
3566 g_clear_error(&error);
3568 g_object_unref(task);
3569 priv->m_nPartId = nPart;
3572 SAL_DLLPUBLIC_EXPORT gchar*
3573 lok_doc_view_get_part_name (LOKDocView* pDocView, int nPart)
3575 LOKDocViewPrivate& priv = getPrivate(pDocView);
3576 if (!priv->m_pDocument)
3577 return nullptr;
3579 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
3580 std::stringstream ss;
3581 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
3582 g_info("%s", ss.str().c_str());
3583 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
3584 return priv->m_pDocument->pClass->getPartName( priv->m_pDocument, nPart );
3587 SAL_DLLPUBLIC_EXPORT void
3588 lok_doc_view_set_partmode(LOKDocView* pDocView,
3589 int nPartMode)
3591 LOKDocViewPrivate& priv = getPrivate(pDocView);
3592 if (!priv->m_pDocument)
3593 return;
3595 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3596 LOEvent* pLOEvent = new LOEvent(LOK_SET_PARTMODE);
3597 GError* error = nullptr;
3599 pLOEvent->m_nPartMode = nPartMode;
3600 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3602 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3603 if (error != nullptr)
3605 g_warning("Unable to call LOK_SET_PARTMODE: %s", error->message);
3606 g_clear_error(&error);
3608 g_object_unref(task);
3611 SAL_DLLPUBLIC_EXPORT void
3612 lok_doc_view_reset_view(LOKDocView* pDocView)
3614 LOKDocViewPrivate& priv = getPrivate(pDocView);
3616 if (priv->m_pTileBuffer != nullptr)
3617 priv->m_pTileBuffer->resetAllTiles();
3618 priv->m_nLoadProgress = 0.0;
3620 memset(&priv->m_aVisibleCursor, 0, sizeof(priv->m_aVisibleCursor));
3621 priv->m_bCursorOverlayVisible = false;
3622 priv->m_bCursorVisible = false;
3624 priv->m_nLastButtonPressTime = 0;
3625 priv->m_nLastButtonReleaseTime = 0;
3626 priv->m_aTextSelectionRectangles.clear();
3628 memset(&priv->m_aTextSelectionStart, 0, sizeof(priv->m_aTextSelectionStart));
3629 memset(&priv->m_aTextSelectionEnd, 0, sizeof(priv->m_aTextSelectionEnd));
3630 memset(&priv->m_aGraphicSelection, 0, sizeof(priv->m_aGraphicSelection));
3631 priv->m_bInDragGraphicSelection = false;
3632 memset(&priv->m_aCellCursor, 0, sizeof(priv->m_aCellCursor));
3634 cairo_surface_destroy(priv->m_pHandleStart);
3635 priv->m_pHandleStart = nullptr;
3636 memset(&priv->m_aHandleStartRect, 0, sizeof(priv->m_aHandleStartRect));
3637 priv->m_bInDragStartHandle = false;
3639 cairo_surface_destroy(priv->m_pHandleMiddle);
3640 priv->m_pHandleMiddle = nullptr;
3641 memset(&priv->m_aHandleMiddleRect, 0, sizeof(priv->m_aHandleMiddleRect));
3642 priv->m_bInDragMiddleHandle = false;
3644 cairo_surface_destroy(priv->m_pHandleEnd);
3645 priv->m_pHandleEnd = nullptr;
3646 memset(&priv->m_aHandleEndRect, 0, sizeof(priv->m_aHandleEndRect));
3647 priv->m_bInDragEndHandle = false;
3649 memset(&priv->m_aGraphicHandleRects, 0, sizeof(priv->m_aGraphicHandleRects));
3650 memset(&priv->m_bInDragGraphicHandles, 0, sizeof(priv->m_bInDragGraphicHandles));
3652 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
3655 SAL_DLLPUBLIC_EXPORT void
3656 lok_doc_view_set_edit(LOKDocView* pDocView,
3657 gboolean bEdit)
3659 LOKDocViewPrivate& priv = getPrivate(pDocView);
3660 if (!priv->m_pDocument)
3661 return;
3663 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3664 LOEvent* pLOEvent = new LOEvent(LOK_SET_EDIT);
3665 GError* error = nullptr;
3667 pLOEvent->m_bEdit = bEdit;
3668 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3670 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3671 if (error != nullptr)
3673 g_warning("Unable to call LOK_SET_EDIT: %s", error->message);
3674 g_clear_error(&error);
3676 g_object_unref(task);
3679 SAL_DLLPUBLIC_EXPORT gboolean
3680 lok_doc_view_get_edit (LOKDocView* pDocView)
3682 LOKDocViewPrivate& priv = getPrivate(pDocView);
3683 return priv->m_bEdit;
3686 SAL_DLLPUBLIC_EXPORT void
3687 lok_doc_view_post_command (LOKDocView* pDocView,
3688 const gchar* pCommand,
3689 const gchar* pArguments,
3690 gboolean bNotifyWhenFinished)
3692 LOKDocViewPrivate& priv = getPrivate(pDocView);
3693 if (!priv->m_pDocument)
3694 return;
3696 if (priv->m_bEdit)
3697 LOKPostCommand(pDocView, pCommand, pArguments, bNotifyWhenFinished);
3698 else
3699 g_info ("LOK_POST_COMMAND: ignoring commands in view-only mode");
3702 SAL_DLLPUBLIC_EXPORT void
3703 lok_doc_view_find_prev (LOKDocView* pDocView,
3704 const gchar* pText,
3705 gboolean bHighlightAll)
3707 doSearch(pDocView, pText, true, bHighlightAll);
3710 SAL_DLLPUBLIC_EXPORT void
3711 lok_doc_view_find_next (LOKDocView* pDocView,
3712 const gchar* pText,
3713 gboolean bHighlightAll)
3715 doSearch(pDocView, pText, false, bHighlightAll);
3718 SAL_DLLPUBLIC_EXPORT void
3719 lok_doc_view_highlight_all (LOKDocView* pDocView,
3720 const gchar* pText)
3722 doSearch(pDocView, pText, false, true);
3725 SAL_DLLPUBLIC_EXPORT gchar*
3726 lok_doc_view_copy_selection (LOKDocView* pDocView,
3727 const gchar* pMimeType,
3728 gchar** pUsedMimeType)
3730 LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(pDocView);
3731 if (!pDocument)
3732 return nullptr;
3734 std::stringstream ss;
3735 ss << "lok::Document::getTextSelection('" << pMimeType << "')";
3736 g_info("%s", ss.str().c_str());
3737 return pDocument->pClass->getTextSelection(pDocument, pMimeType, pUsedMimeType);
3740 SAL_DLLPUBLIC_EXPORT gboolean
3741 lok_doc_view_paste (LOKDocView* pDocView,
3742 const gchar* pMimeType,
3743 const gchar* pData,
3744 gsize nSize)
3746 LOKDocViewPrivate& priv = getPrivate(pDocView);
3747 LibreOfficeKitDocument* pDocument = priv->m_pDocument;
3748 gboolean ret = 0;
3750 if (!pDocument)
3751 return false;
3753 if (!priv->m_bEdit)
3755 g_info ("ignoring paste in view-only mode");
3756 return ret;
3759 if (pData)
3761 std::stringstream ss;
3762 ss << "lok::Document::paste('" << pMimeType << "', '" << std::string(pData, nSize) << ", "<<nSize<<"')";
3763 g_info("%s", ss.str().c_str());
3764 ret = pDocument->pClass->paste(pDocument, pMimeType, pData, nSize);
3767 return ret;
3770 SAL_DLLPUBLIC_EXPORT void
3771 lok_doc_view_set_document_password (LOKDocView* pDocView,
3772 const gchar* pURL,
3773 const gchar* pPassword)
3775 LOKDocViewPrivate& priv = getPrivate(pDocView);
3777 priv->m_pOffice->pClass->setDocumentPassword(priv->m_pOffice, pURL, pPassword);
3780 SAL_DLLPUBLIC_EXPORT gchar*
3781 lok_doc_view_get_version_info (LOKDocView* pDocView)
3783 LOKDocViewPrivate& priv = getPrivate(pDocView);
3785 return priv->m_pOffice->pClass->getVersionInfo(priv->m_pOffice);
3789 SAL_DLLPUBLIC_EXPORT gfloat
3790 lok_doc_view_pixel_to_twip (LOKDocView* pDocView, float fInput)
3792 LOKDocViewPrivate& priv = getPrivate(pDocView);
3793 return pixelToTwip(fInput, priv->m_fZoom);
3796 SAL_DLLPUBLIC_EXPORT gfloat
3797 lok_doc_view_twip_to_pixel (LOKDocView* pDocView, float fInput)
3799 LOKDocViewPrivate& priv = getPrivate(pDocView);
3800 return twipToPixel(fInput, priv->m_fZoom);
3803 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */