Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / libreofficekit / source / gtk / lokdocview.cxx
blob305ac5a477d2abad7b0ed1360ca4cec8184f09cc
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <sal/types.h>
11 #include <math.h>
12 #include <string.h>
13 #include <memory>
14 #include <utility>
15 #include <vector>
16 #include <string>
17 #include <sstream>
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 namespace {
52 /// Same as a GdkRectangle, but also tracks in which part the rectangle is.
53 struct ViewRectangle
55 int m_nPart;
56 GdkRectangle m_aRectangle;
58 ViewRectangle(int nPart = 0, const GdkRectangle& rRectangle = GdkRectangle())
59 : m_nPart(nPart),
60 m_aRectangle(rRectangle)
65 /// Same as a list of GdkRectangles, but also tracks in which part the rectangle is.
66 struct ViewRectangles
68 int m_nPart;
69 std::vector<GdkRectangle> m_aRectangles;
71 ViewRectangles(int nPart = 0, std::vector<GdkRectangle>&& rRectangles = std::vector<GdkRectangle>())
72 : m_nPart(nPart),
73 m_aRectangles(std::move(rRectangles))
78 /// Private struct used by this GObject type
79 struct LOKDocViewPrivateImpl
81 std::string m_aLOPath;
82 std::string m_aUserProfileURL;
83 std::string m_aDocPath;
84 std::string m_aRenderingArguments;
85 gdouble m_nLoadProgress;
86 bool m_bIsLoading;
87 bool m_bInit; // initializeForRendering() has been called
88 bool m_bCanZoomIn;
89 bool m_bCanZoomOut;
90 bool m_bUnipoll;
91 LibreOfficeKit* m_pOffice;
92 LibreOfficeKitDocument* m_pDocument;
94 std::unique_ptr<TileBuffer> m_pTileBuffer;
95 GThreadPool* lokThreadPool;
97 gfloat m_fZoom;
98 glong m_nDocumentWidthTwips;
99 glong m_nDocumentHeightTwips;
100 /// View or edit mode.
101 bool m_bEdit;
102 /// LOK Features
103 guint64 m_nLOKFeatures;
104 /// Number of parts in currently loaded document
105 gint m_nParts;
106 /// Position and size of the visible cursor.
107 GdkRectangle m_aVisibleCursor;
108 /// Position and size of the view cursors. The current view can only see
109 /// them, can't modify them. Key is the view id.
110 std::map<int, ViewRectangle> m_aViewCursors;
111 /// Cursor overlay is visible or hidden (for blinking).
112 bool m_bCursorOverlayVisible;
113 /// Cursor is visible or hidden (e.g. for graphic selection).
114 bool m_bCursorVisible;
115 /// Visibility of view selections. The current view can only see / them,
116 /// can't modify them. Key is the view id.
117 std::map<int, bool> m_aViewCursorVisibilities;
118 /// Time of the last button press.
119 guint32 m_nLastButtonPressTime;
120 /// Time of the last button release.
121 guint32 m_nLastButtonReleaseTime;
122 /// Last pressed button (left, right, middle)
123 guint32 m_nLastButtonPressed;
124 /// Key modifier (ctrl, atl, shift)
125 guint32 m_nKeyModifier;
126 /// Rectangles of the current text selection.
127 std::vector<GdkRectangle> m_aTextSelectionRectangles;
128 /// Rectangles of the current content control.
129 std::vector<GdkRectangle> m_aContentControlRectangles;
130 /// Alias/title of the current content control.
131 std::string m_aContentControlAlias;
132 /// Rectangles of view selections. The current view can only see
133 /// them, can't modify them. Key is the view id.
134 std::map<int, ViewRectangles> m_aTextViewSelectionRectangles;
135 /// Position and size of the selection start (as if there would be a cursor caret there).
136 GdkRectangle m_aTextSelectionStart;
137 /// Position and size of the selection end.
138 GdkRectangle m_aTextSelectionEnd;
139 GdkRectangle m_aGraphicSelection;
140 /// Position and size of the graphic view selections. The current view can only
141 /// see them, can't modify them. Key is the view id.
142 std::map<int, ViewRectangle> m_aGraphicViewSelections;
143 GdkRectangle m_aCellCursor;
144 /// Position and size of the cell view cursors. The current view can only
145 /// see them, can't modify them. Key is the view id.
146 std::map<int, ViewRectangle> m_aCellViewCursors;
147 bool m_bInDragGraphicSelection;
148 /// Position, size and color of the reference marks. The current view can only
149 /// see them, can't modify them. Key is the view id.
150 std::vector<std::pair<ViewRectangle, sal_uInt32>> m_aReferenceMarks;
152 /// @name Start/middle/end handle.
153 ///@{
154 /// Bitmap of the text selection start handle.
155 cairo_surface_t* m_pHandleStart;
156 /// Rectangle of the text selection start handle, to know if the user clicked on it or not
157 GdkRectangle m_aHandleStartRect;
158 /// If we are in the middle of a drag of the text selection end handle.
159 bool m_bInDragStartHandle;
160 /// Bitmap of the text selection middle handle.
161 cairo_surface_t* m_pHandleMiddle;
162 /// Rectangle of the text selection middle handle, to know if the user clicked on it or not
163 GdkRectangle m_aHandleMiddleRect;
164 /// If we are in the middle of a drag of the text selection middle handle.
165 bool m_bInDragMiddleHandle;
166 /// Bitmap of the text selection end handle.
167 cairo_surface_t* m_pHandleEnd;
168 /// Rectangle of the text selection end handle, to know if the user clicked on it or not
169 GdkRectangle m_aHandleEndRect;
170 /// If we are in the middle of a drag of the text selection end handle.
171 bool m_bInDragEndHandle;
172 ///@}
174 /// @name Graphic handles.
175 ///@{
176 /// Rectangle of a graphic selection handle, to know if the user clicked on it or not.
177 GdkRectangle m_aGraphicHandleRects[8];
178 /// If we are in the middle of a drag of a graphic selection handle.
179 bool m_bInDragGraphicHandles[8];
180 ///@}
182 /// View ID, returned by createView() or 0 by default.
183 int m_nViewId;
185 /// Cached part ID, returned by getPart().
186 int m_nPartId;
188 /// Cached document type, returned by getDocumentType().
189 LibreOfficeKitDocumentType m_eDocumentType;
191 /// Contains a freshly set zoom level: logic size of a tile.
192 /// It gets reset back to 0 when LOK was informed about this zoom change.
193 int m_nTileSizeTwips;
195 GdkRectangle m_aVisibleArea;
196 bool m_bVisibleAreaSet;
198 /// Event source ID for handleTimeout() of this widget.
199 guint m_nTimeoutId;
201 /// Rectangles of view locks. The current view can only see
202 /// them, can't modify them. Key is the view id.
203 std::map<int, ViewRectangle> m_aViewLockRectangles;
205 LOKDocViewPrivateImpl()
206 : m_nLoadProgress(0),
207 m_bIsLoading(false),
208 m_bInit(false),
209 m_bCanZoomIn(true),
210 m_bCanZoomOut(true),
211 m_bUnipoll(false),
212 m_pOffice(nullptr),
213 m_pDocument(nullptr),
214 lokThreadPool(nullptr),
215 m_fZoom(0),
216 m_nDocumentWidthTwips(0),
217 m_nDocumentHeightTwips(0),
218 m_bEdit(false),
219 m_nLOKFeatures(0),
220 m_nParts(0),
221 m_aVisibleCursor({0, 0, 0, 0}),
222 m_bCursorOverlayVisible(false),
223 m_bCursorVisible(true),
224 m_nLastButtonPressTime(0),
225 m_nLastButtonReleaseTime(0),
226 m_nLastButtonPressed(0),
227 m_nKeyModifier(0),
228 m_aTextSelectionStart({0, 0, 0, 0}),
229 m_aTextSelectionEnd({0, 0, 0, 0}),
230 m_aGraphicSelection({0, 0, 0, 0}),
231 m_aCellCursor({0, 0, 0, 0}),
232 m_bInDragGraphicSelection(false),
233 m_pHandleStart(nullptr),
234 m_aHandleStartRect({0, 0, 0, 0}),
235 m_bInDragStartHandle(false),
236 m_pHandleMiddle(nullptr),
237 m_aHandleMiddleRect({0, 0, 0, 0}),
238 m_bInDragMiddleHandle(false),
239 m_pHandleEnd(nullptr),
240 m_aHandleEndRect({0, 0, 0, 0}),
241 m_bInDragEndHandle(false),
242 m_nViewId(0),
243 m_nPartId(0),
244 m_eDocumentType(LOK_DOCTYPE_OTHER),
245 m_nTileSizeTwips(0),
246 m_aVisibleArea({0, 0, 0, 0}),
247 m_bVisibleAreaSet(false),
248 m_nTimeoutId(0)
250 memset(&m_aGraphicHandleRects, 0, sizeof(m_aGraphicHandleRects));
251 memset(&m_bInDragGraphicHandles, 0, sizeof(m_bInDragGraphicHandles));
254 ~LOKDocViewPrivateImpl()
256 if (m_nTimeoutId)
257 g_source_remove(m_nTimeoutId);
261 // Must be run with g_aLOKMutex locked
262 void setDocumentView(LibreOfficeKitDocument* pDoc, int viewId)
264 assert(pDoc);
265 std::stringstream ss;
266 ss << "lok::Document::setView(" << viewId << ")";
267 g_info("%s", ss.str().c_str());
268 pDoc->pClass->setView(pDoc, viewId);
272 /// Wrapper around LOKDocViewPrivateImpl, managed by malloc/memset/free.
273 struct _LOKDocViewPrivate
275 LOKDocViewPrivateImpl* m_pImpl;
277 LOKDocViewPrivateImpl* operator->()
279 return m_pImpl;
283 enum
285 LOAD_CHANGED,
286 EDIT_CHANGED,
287 COMMAND_CHANGED,
288 SEARCH_NOT_FOUND,
289 PART_CHANGED,
290 SIZE_CHANGED,
291 HYPERLINK_CLICKED,
292 CURSOR_CHANGED,
293 SEARCH_RESULT_COUNT,
294 COMMAND_RESULT,
295 ADDRESS_CHANGED,
296 FORMULA_CHANGED,
297 TEXT_SELECTION,
298 CONTENT_CONTROL,
299 PASSWORD_REQUIRED,
300 COMMENT,
301 RULER,
302 WINDOW,
303 INVALIDATE_HEADER,
305 LAST_SIGNAL
308 enum
310 PROP_0,
312 PROP_LO_PATH,
313 PROP_LO_UNIPOLL,
314 PROP_LO_POINTER,
315 PROP_USER_PROFILE_URL,
316 PROP_DOC_PATH,
317 PROP_DOC_POINTER,
318 PROP_EDITABLE,
319 PROP_LOAD_PROGRESS,
320 PROP_ZOOM,
321 PROP_IS_LOADING,
322 PROP_IS_INITIALIZED,
323 PROP_DOC_WIDTH,
324 PROP_DOC_HEIGHT,
325 PROP_CAN_ZOOM_IN,
326 PROP_CAN_ZOOM_OUT,
327 PROP_DOC_PASSWORD,
328 PROP_DOC_PASSWORD_TO_MODIFY,
329 PROP_TILED_ANNOTATIONS,
331 PROP_LAST
334 static guint doc_view_signals[LAST_SIGNAL] = { 0 };
335 static GParamSpec *properties[PROP_LAST] = { nullptr };
337 static void lok_doc_view_initable_iface_init (GInitableIface *iface);
338 static void callbackWorker (int nType, const char* pPayload, void* pData);
339 static void updateClientZoom (LOKDocView *pDocView);
341 #ifdef __GNUC__
342 #pragma GCC diagnostic push
343 #pragma GCC diagnostic ignored "-Wunused-function"
344 #if defined __clang__
345 #if __has_warning("-Wdeprecated-volatile")
346 #pragma clang diagnostic ignored "-Wdeprecated-volatile"
347 #endif
348 #endif
349 #endif
350 G_DEFINE_TYPE_WITH_CODE (LOKDocView, lok_doc_view, GTK_TYPE_DRAWING_AREA,
351 G_ADD_PRIVATE (LOKDocView)
352 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, lok_doc_view_initable_iface_init));
353 #ifdef __GNUC__
354 #pragma GCC diagnostic pop
355 #endif
357 static LOKDocViewPrivate& getPrivate(LOKDocView* pDocView)
359 LOKDocViewPrivate* priv = static_cast<LOKDocViewPrivate*>(lok_doc_view_get_instance_private(pDocView));
360 return *priv;
363 namespace {
365 /// Helper struct used to pass the data from soffice thread -> main thread.
366 struct CallbackData
368 int m_nType;
369 std::string m_aPayload;
370 LOKDocView* m_pDocView;
372 CallbackData(int nType, std::string aPayload, LOKDocView* pDocView)
373 : m_nType(nType),
374 m_aPayload(std::move(aPayload)),
375 m_pDocView(pDocView) {}
380 static void
381 LOKPostCommand (LOKDocView* pDocView,
382 const gchar* pCommand,
383 const gchar* pArguments,
384 bool bNotifyWhenFinished)
386 LOKDocViewPrivate& priv = getPrivate(pDocView);
387 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
388 LOEvent* pLOEvent = new LOEvent(LOK_POST_COMMAND);
389 GError* error = nullptr;
390 pLOEvent->m_pCommand = g_strdup(pCommand);
391 pLOEvent->m_pArguments = g_strdup(pArguments);
392 pLOEvent->m_bNotifyWhenFinished = bNotifyWhenFinished;
394 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
395 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
396 if (error != nullptr)
398 g_warning("Unable to call LOK_POST_COMMAND: %s", error->message);
399 g_clear_error(&error);
401 g_object_unref(task);
404 static void
405 doSearch(LOKDocView* pDocView, const char* pText, bool bBackwards, bool highlightAll)
407 LOKDocViewPrivate& priv = getPrivate(pDocView);
408 if (!priv->m_pDocument)
409 return;
411 boost::property_tree::ptree aTree;
412 GtkWidget* drawingWidget = GTK_WIDGET(pDocView);
413 GdkWindow* drawingWindow = gtk_widget_get_window(drawingWidget);
414 if (!drawingWindow)
415 return;
416 std::shared_ptr<cairo_region_t> cairoVisRegion( gdk_window_get_visible_region(drawingWindow),
417 cairo_region_destroy);
418 cairo_rectangle_int_t cairoVisRect;
419 cairo_region_get_rectangle(cairoVisRegion.get(), 0, &cairoVisRect);
420 int x = pixelToTwip (cairoVisRect.x, priv->m_fZoom);
421 int y = pixelToTwip (cairoVisRect.y, priv->m_fZoom);
423 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchString/type", '/'), "string");
424 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchString/value", '/'), pText);
425 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Backward/type", '/'), "boolean");
426 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Backward/value", '/'), bBackwards);
427 if (highlightAll)
429 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Command/type", '/'), "unsigned short");
430 // SvxSearchCmd::FIND_ALL
431 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Command/value", '/'), "1");
434 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointX/type", '/'), "long");
435 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointX/value", '/'), x);
436 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointY/type", '/'), "long");
437 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointY/value", '/'), y);
439 std::stringstream aStream;
440 boost::property_tree::write_json(aStream, aTree);
442 LOKPostCommand (pDocView, ".uno:ExecuteSearch", aStream.str().c_str(), false);
445 static bool
446 isEmptyRectangle(const GdkRectangle& rRectangle)
448 return rRectangle.x == 0 && rRectangle.y == 0 && rRectangle.width == 0 && rRectangle.height == 0;
451 /// if handled, returns TRUE else FALSE
452 static bool
453 handleTextSelectionOnButtonPress(GdkRectangle& aClick, LOKDocView* pDocView) {
454 LOKDocViewPrivate& priv = getPrivate(pDocView);
456 if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleStartRect, nullptr))
458 g_info("LOKDocView_Impl::signalButton: start of drag start handle");
459 priv->m_bInDragStartHandle = true;
460 return true;
462 else if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleMiddleRect, nullptr))
464 g_info("LOKDocView_Impl::signalButton: start of drag middle handle");
465 priv->m_bInDragMiddleHandle = true;
466 return true;
468 else if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleEndRect, nullptr))
470 g_info("LOKDocView_Impl::signalButton: start of drag end handle");
471 priv->m_bInDragEndHandle = true;
472 return true;
475 return false;
478 /// if handled, returns TRUE else FALSE
479 static bool
480 handleGraphicSelectionOnButtonPress(GdkRectangle& aClick, LOKDocView* pDocView) {
481 LOKDocViewPrivate& priv = getPrivate(pDocView);
482 GError* error = nullptr;
484 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
486 if (gdk_rectangle_intersect(&aClick, &priv->m_aGraphicHandleRects[i], nullptr))
488 g_info("LOKDocView_Impl::signalButton: start of drag graphic handle #%d", i);
489 priv->m_bInDragGraphicHandles[i] = true;
491 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
492 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
493 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_START;
494 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(priv->m_aGraphicHandleRects[i].x + priv->m_aGraphicHandleRects[i].width / 2, priv->m_fZoom);
495 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(priv->m_aGraphicHandleRects[i].y + priv->m_aGraphicHandleRects[i].height / 2, priv->m_fZoom);
496 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
498 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
499 if (error != nullptr)
501 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
502 g_clear_error(&error);
504 g_object_unref(task);
506 return true;
510 return false;
513 /// if handled, returns TRUE else FALSE
514 static bool
515 handleTextSelectionOnButtonRelease(LOKDocView* pDocView) {
516 LOKDocViewPrivate& priv = getPrivate(pDocView);
518 if (priv->m_bInDragStartHandle)
520 g_info("LOKDocView_Impl::signalButton: end of drag start handle");
521 priv->m_bInDragStartHandle = false;
522 return true;
524 else if (priv->m_bInDragMiddleHandle)
526 g_info("LOKDocView_Impl::signalButton: end of drag middle handle");
527 priv->m_bInDragMiddleHandle = false;
528 return true;
530 else if (priv->m_bInDragEndHandle)
532 g_info("LOKDocView_Impl::signalButton: end of drag end handle");
533 priv->m_bInDragEndHandle = false;
534 return true;
537 return false;
540 /// if handled, returns TRUE else FALSE
541 static bool
542 handleGraphicSelectionOnButtonRelease(LOKDocView* pDocView, GdkEventButton* pEvent) {
543 LOKDocViewPrivate& priv = getPrivate(pDocView);
544 GError* error = nullptr;
546 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
548 if (priv->m_bInDragGraphicHandles[i])
550 g_info("LOKDocView_Impl::signalButton: end of drag graphic handle #%d", i);
551 priv->m_bInDragGraphicHandles[i] = false;
553 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
554 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
555 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_END;
556 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(pEvent->x, priv->m_fZoom);
557 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(pEvent->y, priv->m_fZoom);
558 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
560 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
561 if (error != nullptr)
563 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
564 g_clear_error(&error);
566 g_object_unref(task);
568 return true;
572 if (!priv->m_bInDragGraphicSelection)
573 return false;
575 g_info("LOKDocView_Impl::signalButton: end of drag graphic selection");
576 priv->m_bInDragGraphicSelection = false;
578 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
579 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
580 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_END;
581 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(pEvent->x, priv->m_fZoom);
582 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(pEvent->y, priv->m_fZoom);
583 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
585 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
586 if (error != nullptr)
588 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
589 g_clear_error(&error);
591 g_object_unref(task);
593 return true;
596 static void
597 postKeyEventInThread(gpointer data)
599 GTask* task = G_TASK(data);
600 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
601 LOKDocViewPrivate& priv = getPrivate(pDocView);
602 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
603 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
604 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
606 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
607 setDocumentView(priv->m_pDocument, priv->m_nViewId);
608 std::stringstream ss;
610 if (priv->m_nTileSizeTwips)
612 ss.str(std::string());
613 ss << "lok::Document::setClientZoom(" << nTileSizePixelsScaled << ", " << nTileSizePixelsScaled << ", " << priv->m_nTileSizeTwips << ", " << priv->m_nTileSizeTwips << ")";
614 g_info("%s", ss.str().c_str());
615 priv->m_pDocument->pClass->setClientZoom(priv->m_pDocument,
616 nTileSizePixelsScaled,
617 nTileSizePixelsScaled,
618 priv->m_nTileSizeTwips,
619 priv->m_nTileSizeTwips);
620 priv->m_nTileSizeTwips = 0;
622 if (priv->m_bVisibleAreaSet)
624 ss.str(std::string());
625 ss << "lok::Document::setClientVisibleArea(" << priv->m_aVisibleArea.x << ", " << priv->m_aVisibleArea.y << ", ";
626 ss << priv->m_aVisibleArea.width << ", " << priv->m_aVisibleArea.height << ")";
627 g_info("%s", ss.str().c_str());
628 priv->m_pDocument->pClass->setClientVisibleArea(priv->m_pDocument,
629 priv->m_aVisibleArea.x,
630 priv->m_aVisibleArea.y,
631 priv->m_aVisibleArea.width,
632 priv->m_aVisibleArea.height);
633 priv->m_bVisibleAreaSet = false;
636 ss.str(std::string());
637 ss << "lok::Document::postKeyEvent(" << pLOEvent->m_nKeyEvent << ", " << pLOEvent->m_nCharCode << ", " << pLOEvent->m_nKeyCode << ")";
638 g_info("%s", ss.str().c_str());
639 priv->m_pDocument->pClass->postKeyEvent(priv->m_pDocument,
640 pLOEvent->m_nKeyEvent,
641 pLOEvent->m_nCharCode,
642 pLOEvent->m_nKeyCode);
645 static gboolean
646 signalKey (GtkWidget* pWidget, GdkEventKey* pEvent)
648 LOKDocView* pDocView = LOK_DOC_VIEW(pWidget);
649 LOKDocViewPrivate& priv = getPrivate(pDocView);
650 int nCharCode = 0;
651 int nKeyCode = 0;
652 GError* error = nullptr;
654 if (!priv->m_bEdit)
656 g_info("signalKey: not in edit mode, ignore");
657 return FALSE;
660 priv->m_nKeyModifier &= KEY_MOD2;
661 switch (pEvent->keyval)
663 case GDK_KEY_BackSpace:
664 nKeyCode = com::sun::star::awt::Key::BACKSPACE;
665 break;
666 case GDK_KEY_Delete:
667 nKeyCode = com::sun::star::awt::Key::DELETE;
668 break;
669 case GDK_KEY_Return:
670 case GDK_KEY_KP_Enter:
671 nKeyCode = com::sun::star::awt::Key::RETURN;
672 break;
673 case GDK_KEY_Escape:
674 nKeyCode = com::sun::star::awt::Key::ESCAPE;
675 break;
676 case GDK_KEY_Tab:
677 nKeyCode = com::sun::star::awt::Key::TAB;
678 break;
679 case GDK_KEY_Down:
680 nKeyCode = com::sun::star::awt::Key::DOWN;
681 break;
682 case GDK_KEY_Up:
683 nKeyCode = com::sun::star::awt::Key::UP;
684 break;
685 case GDK_KEY_Left:
686 nKeyCode = com::sun::star::awt::Key::LEFT;
687 break;
688 case GDK_KEY_Right:
689 nKeyCode = com::sun::star::awt::Key::RIGHT;
690 break;
691 case GDK_KEY_Page_Down:
692 nKeyCode = com::sun::star::awt::Key::PAGEDOWN;
693 break;
694 case GDK_KEY_Page_Up:
695 nKeyCode = com::sun::star::awt::Key::PAGEUP;
696 break;
697 case GDK_KEY_Insert:
698 nKeyCode = com::sun::star::awt::Key::INSERT;
699 break;
700 case GDK_KEY_Shift_L:
701 case GDK_KEY_Shift_R:
702 if (pEvent->type == GDK_KEY_PRESS)
703 priv->m_nKeyModifier |= KEY_SHIFT;
704 break;
705 case GDK_KEY_Control_L:
706 case GDK_KEY_Control_R:
707 if (pEvent->type == GDK_KEY_PRESS)
708 priv->m_nKeyModifier |= KEY_MOD1;
709 break;
710 case GDK_KEY_Alt_L:
711 case GDK_KEY_Alt_R:
712 if (pEvent->type == GDK_KEY_PRESS)
713 priv->m_nKeyModifier |= KEY_MOD2;
714 else
715 priv->m_nKeyModifier &= ~KEY_MOD2;
716 break;
717 default:
718 if (pEvent->keyval >= GDK_KEY_F1 && pEvent->keyval <= GDK_KEY_F26)
719 nKeyCode = com::sun::star::awt::Key::F1 + (pEvent->keyval - GDK_KEY_F1);
720 else
721 nCharCode = gdk_keyval_to_unicode(pEvent->keyval);
724 // rsc is not public API, but should be good enough for debugging purposes.
725 // If this is needed for real, then probably a new param of type
726 // css::awt::KeyModifier is needed in postKeyEvent().
727 if (pEvent->state & GDK_SHIFT_MASK)
728 nKeyCode |= KEY_SHIFT;
730 if (pEvent->state & GDK_CONTROL_MASK)
731 nKeyCode |= KEY_MOD1;
733 if (priv->m_nKeyModifier & KEY_MOD2)
734 nKeyCode |= KEY_MOD2;
736 if (nKeyCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2)) {
737 if (pEvent->keyval >= GDK_KEY_a && pEvent->keyval <= GDK_KEY_z)
739 nKeyCode |= 512 + (pEvent->keyval - GDK_KEY_a);
741 else if (pEvent->keyval >= GDK_KEY_A && pEvent->keyval <= GDK_KEY_Z) {
742 nKeyCode |= 512 + (pEvent->keyval - GDK_KEY_A);
744 else if (pEvent->keyval >= GDK_KEY_0 && pEvent->keyval <= GDK_KEY_9) {
745 nKeyCode |= 256 + (pEvent->keyval - GDK_KEY_0);
749 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
750 LOEvent* pLOEvent = new LOEvent(LOK_POST_KEY);
751 pLOEvent->m_nKeyEvent = pEvent->type == GDK_KEY_RELEASE ? LOK_KEYEVENT_KEYUP : LOK_KEYEVENT_KEYINPUT;
752 pLOEvent->m_nCharCode = nCharCode;
753 pLOEvent->m_nKeyCode = nKeyCode;
754 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
755 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
756 if (error != nullptr)
758 g_warning("Unable to call LOK_POST_KEY: %s", error->message);
759 g_clear_error(&error);
761 g_object_unref(task);
763 return FALSE;
766 static gboolean
767 handleTimeout (gpointer pData)
769 LOKDocView* pDocView = LOK_DOC_VIEW (pData);
770 LOKDocViewPrivate& priv = getPrivate(pDocView);
772 if (priv->m_bEdit)
774 if (priv->m_bCursorOverlayVisible)
775 priv->m_bCursorOverlayVisible = false;
776 else
777 priv->m_bCursorOverlayVisible = true;
778 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
781 return G_SOURCE_CONTINUE;
784 static void
785 commandChanged(LOKDocView* pDocView, const std::string& rString)
787 g_signal_emit(pDocView, doc_view_signals[COMMAND_CHANGED], 0, rString.c_str());
790 static void
791 searchNotFound(LOKDocView* pDocView, const std::string& rString)
793 g_signal_emit(pDocView, doc_view_signals[SEARCH_NOT_FOUND], 0, rString.c_str());
796 static void searchResultCount(LOKDocView* pDocView, const std::string& rString)
798 g_signal_emit(pDocView, doc_view_signals[SEARCH_RESULT_COUNT], 0, rString.c_str());
801 static void commandResult(LOKDocView* pDocView, const std::string& rString)
803 g_signal_emit(pDocView, doc_view_signals[COMMAND_RESULT], 0, rString.c_str());
806 static void addressChanged(LOKDocView* pDocView, const std::string& rString)
808 g_signal_emit(pDocView, doc_view_signals[ADDRESS_CHANGED], 0, rString.c_str());
811 static void formulaChanged(LOKDocView* pDocView, const std::string& rString)
813 g_signal_emit(pDocView, doc_view_signals[FORMULA_CHANGED], 0, rString.c_str());
816 static void reportError(LOKDocView* /*pDocView*/, const std::string& rString)
818 GtkWidget *dialog = gtk_message_dialog_new(nullptr,
819 GTK_DIALOG_DESTROY_WITH_PARENT,
820 GTK_MESSAGE_ERROR,
821 GTK_BUTTONS_CLOSE,
822 "%s",
823 rString.c_str());
824 gtk_dialog_run(GTK_DIALOG(dialog));
825 gtk_widget_destroy(dialog);
828 static void
829 setPart(LOKDocView* pDocView, const std::string& rString)
831 LOKDocViewPrivate& priv = getPrivate(pDocView);
832 priv->m_nPartId = std::stoi(rString);
833 g_signal_emit(pDocView, doc_view_signals[PART_CHANGED], 0, priv->m_nPartId);
836 static void
837 hyperlinkClicked(LOKDocView* pDocView, const std::string& rString)
839 g_signal_emit(pDocView, doc_view_signals[HYPERLINK_CLICKED], 0, rString.c_str());
842 /// Trigger a redraw, invoked on the main thread by other functions running in a thread.
843 static gboolean queueDraw(gpointer pData)
845 GtkWidget* pWidget = static_cast<GtkWidget*>(pData);
847 gtk_widget_queue_draw(pWidget);
849 return G_SOURCE_REMOVE;
852 /// Looks up the author string from initializeForRendering()'s rendering arguments.
853 static std::string getAuthorRenderingArgument(LOKDocViewPrivate& priv)
855 std::stringstream aStream;
856 aStream << priv->m_aRenderingArguments;
857 boost::property_tree::ptree aTree;
858 boost::property_tree::read_json(aStream, aTree);
859 std::string aRet;
860 for (const auto& rPair : aTree)
862 if (rPair.first == ".uno:Author")
864 aRet = rPair.second.get<std::string>("value");
865 break;
868 return aRet;
871 /// Author string <-> View ID map
872 static std::map<std::string, int> g_aAuthorViews;
874 static void refreshSize(LOKDocView* pDocView)
876 LOKDocViewPrivate& priv = getPrivate(pDocView);
878 priv->m_pDocument->pClass->getDocumentSize(priv->m_pDocument, &priv->m_nDocumentWidthTwips, &priv->m_nDocumentHeightTwips);
879 float zoom = priv->m_fZoom;
880 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
881 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
882 long nDocumentWidthTwips = priv->m_nDocumentWidthTwips;
883 long nDocumentHeightTwips = priv->m_nDocumentHeightTwips;
884 long nDocumentWidthPixels = twipToPixel(nDocumentWidthTwips, zoom);
885 long nDocumentHeightPixels = twipToPixel(nDocumentHeightTwips, zoom);
887 // Total number of columns in this document.
888 guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixelsScaled);
889 priv->m_pTileBuffer = std::make_unique<TileBuffer>(nColumns, nScaleFactor);
890 gtk_widget_set_size_request(GTK_WIDGET(pDocView),
891 nDocumentWidthPixels,
892 nDocumentHeightPixels);
895 /// Set up LOKDocView after the document is loaded, invoked on the main thread by openDocumentInThread() running in a thread.
896 static gboolean postDocumentLoad(gpointer pData)
898 LOKDocView* pLOKDocView = static_cast<LOKDocView*>(pData);
899 LOKDocViewPrivate& priv = getPrivate(pLOKDocView);
901 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
902 priv->m_pDocument->pClass->initializeForRendering(priv->m_pDocument, priv->m_aRenderingArguments.c_str());
903 // This returns the view id of the "current" view, but sadly if you load multiple documents that
904 // is apparently not a view showing the most recently loaded document. Not much we can do here,
905 // though. If that is fixed, this comment becomes incorrect.
906 priv->m_nViewId = priv->m_pDocument->pClass->getView(priv->m_pDocument);
907 g_aAuthorViews[getAuthorRenderingArgument(priv)] = priv->m_nViewId;
908 priv->m_pDocument->pClass->registerCallback(priv->m_pDocument, callbackWorker, pLOKDocView);
909 priv->m_nParts = priv->m_pDocument->pClass->getParts(priv->m_pDocument);
910 aGuard.unlock();
911 priv->m_nTimeoutId = g_timeout_add(600, handleTimeout, pLOKDocView);
913 refreshSize(pLOKDocView);
915 gtk_widget_set_can_focus(GTK_WIDGET(pLOKDocView), true);
916 gtk_widget_grab_focus(GTK_WIDGET(pLOKDocView));
917 lok_doc_view_set_zoom(pLOKDocView, 1.0);
919 // we are completely loaded
920 priv->m_bInit = true;
921 g_object_notify_by_pspec(G_OBJECT(pLOKDocView), properties[PROP_IS_INITIALIZED]);
923 return G_SOURCE_REMOVE;
926 /// Implementation of the global callback handler, invoked by globalCallback();
927 static gboolean
928 globalCallback (gpointer pData)
930 CallbackData* pCallback = static_cast<CallbackData*>(pData);
931 LOKDocViewPrivate& priv = getPrivate(pCallback->m_pDocView);
932 bool bModify = false;
934 switch (pCallback->m_nType)
936 case LOK_CALLBACK_STATUS_INDICATOR_START:
938 priv->m_nLoadProgress = 0.0;
939 g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, 0.0);
941 break;
942 case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
944 priv->m_nLoadProgress = static_cast<gdouble>(std::stoi(pCallback->m_aPayload)/100.0);
945 g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, priv->m_nLoadProgress);
947 break;
948 case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
950 priv->m_nLoadProgress = 1.0;
951 g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, 1.0);
953 break;
954 case LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY:
955 bModify = true;
956 [[fallthrough]];
957 case LOK_CALLBACK_DOCUMENT_PASSWORD:
959 char const*const pURL(pCallback->m_aPayload.c_str());
960 g_signal_emit (pCallback->m_pDocView, doc_view_signals[PASSWORD_REQUIRED], 0, pURL, bModify);
962 break;
963 case LOK_CALLBACK_ERROR:
965 reportError(pCallback->m_pDocView, pCallback->m_aPayload);
967 break;
968 case LOK_CALLBACK_SIGNATURE_STATUS:
970 // TODO
972 break;
973 default:
974 g_assert(false);
975 break;
977 delete pCallback;
979 return G_SOURCE_REMOVE;
982 static void
983 globalCallbackWorker(int nType, const char* pPayload, void* pData)
985 LOKDocView* pDocView = LOK_DOC_VIEW (pData);
987 CallbackData* pCallback = new CallbackData(nType, pPayload ? pPayload : "(nil)", pDocView);
988 g_info("LOKDocView_Impl::globalCallbackWorkerImpl: %s, '%s'", lokCallbackTypeToString(nType), pPayload);
989 gdk_threads_add_idle(globalCallback, pCallback);
992 static GdkRectangle
993 payloadToRectangle (LOKDocView* pDocView, const char* pPayload)
995 LOKDocViewPrivate& priv = getPrivate(pDocView);
996 GdkRectangle aRet;
997 // x, y, width, height, part number.
998 gchar** ppCoordinates = g_strsplit(pPayload, ", ", 5);
999 gchar** ppCoordinate = ppCoordinates;
1001 aRet.width = aRet.height = aRet.x = aRet.y = 0;
1003 if (!*ppCoordinate)
1005 g_strfreev(ppCoordinates);
1006 return aRet;
1008 aRet.x = atoi(*ppCoordinate);
1009 if (aRet.x < 0)
1010 aRet.x = 0;
1011 ++ppCoordinate;
1012 if (!*ppCoordinate)
1014 g_strfreev(ppCoordinates);
1015 return aRet;
1017 aRet.y = atoi(*ppCoordinate);
1018 if (aRet.y < 0)
1019 aRet.y = 0;
1020 ++ppCoordinate;
1021 if (!*ppCoordinate)
1023 g_strfreev(ppCoordinates);
1024 return aRet;
1026 long l = atol(*ppCoordinate);
1027 if (l > std::numeric_limits<int>::max())
1028 aRet.width = std::numeric_limits<int>::max();
1029 else
1030 aRet.width = l;
1031 if (aRet.x + aRet.width > priv->m_nDocumentWidthTwips)
1032 aRet.width = priv->m_nDocumentWidthTwips - aRet.x;
1033 ++ppCoordinate;
1034 if (!*ppCoordinate)
1036 g_strfreev(ppCoordinates);
1037 return aRet;
1039 l = atol(*ppCoordinate);
1040 if (l > std::numeric_limits<int>::max())
1041 aRet.height = std::numeric_limits<int>::max();
1042 else
1043 aRet.height = l;
1044 if (aRet.y + aRet.height > priv->m_nDocumentHeightTwips)
1045 aRet.height = priv->m_nDocumentHeightTwips - aRet.y;
1047 g_strfreev(ppCoordinates);
1048 return aRet;
1051 static std::vector<GdkRectangle>
1052 payloadToRectangles(LOKDocView* pDocView, const char* pPayload)
1054 std::vector<GdkRectangle> aRet;
1056 if (g_strcmp0(pPayload, "EMPTY") == 0)
1057 return aRet;
1059 gchar** ppRectangles = g_strsplit(pPayload, "; ", 0);
1060 for (gchar** ppRectangle = ppRectangles; *ppRectangle; ++ppRectangle)
1061 aRet.push_back(payloadToRectangle(pDocView, *ppRectangle));
1062 g_strfreev(ppRectangles);
1064 return aRet;
1068 static void
1069 setTilesInvalid (LOKDocView* pDocView, const GdkRectangle& rRectangle)
1071 LOKDocViewPrivate& priv = getPrivate(pDocView);
1072 GdkRectangle aRectanglePixels;
1073 GdkPoint aStart, aEnd;
1074 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
1075 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
1077 aRectanglePixels.x = twipToPixel(rRectangle.x, priv->m_fZoom) * nScaleFactor;
1078 aRectanglePixels.y = twipToPixel(rRectangle.y, priv->m_fZoom) * nScaleFactor;
1079 aRectanglePixels.width = twipToPixel(rRectangle.width, priv->m_fZoom) * nScaleFactor;
1080 aRectanglePixels.height = twipToPixel(rRectangle.height, priv->m_fZoom) * nScaleFactor;
1082 aStart.x = aRectanglePixels.y / nTileSizePixelsScaled;
1083 aStart.y = aRectanglePixels.x / nTileSizePixelsScaled;
1084 aEnd.x = (aRectanglePixels.y + aRectanglePixels.height + nTileSizePixelsScaled) / nTileSizePixelsScaled;
1085 aEnd.y = (aRectanglePixels.x + aRectanglePixels.width + nTileSizePixelsScaled) / nTileSizePixelsScaled;
1086 for (int i = aStart.x; i < aEnd.x; i++)
1088 for (int j = aStart.y; j < aEnd.y; j++)
1090 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
1091 priv->m_pTileBuffer->setInvalid(i, j, priv->m_fZoom, task, priv->lokThreadPool);
1092 g_object_unref(task);
1097 static gboolean
1098 callback (gpointer pData)
1100 CallbackData* pCallback = static_cast<CallbackData*>(pData);
1101 LOKDocView* pDocView = LOK_DOC_VIEW (pCallback->m_pDocView);
1102 LOKDocViewPrivate& priv = getPrivate(pDocView);
1104 //callback registered before the widget was destroyed.
1105 //Use existence of lokThreadPool as flag it was torn down
1106 if (!priv->lokThreadPool)
1108 delete pCallback;
1109 return G_SOURCE_REMOVE;
1112 switch (static_cast<LibreOfficeKitCallbackType>(pCallback->m_nType))
1114 case LOK_CALLBACK_INVALIDATE_TILES:
1116 if (pCallback->m_aPayload.compare(0, 5, "EMPTY") != 0) // payload doesn't start with "EMPTY"
1118 GdkRectangle aRectangle = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1119 setTilesInvalid(pDocView, aRectangle);
1121 else
1122 priv->m_pTileBuffer->resetAllTiles();
1124 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1126 break;
1127 case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
1130 std::stringstream aStream(pCallback->m_aPayload);
1131 boost::property_tree::ptree aTree;
1132 boost::property_tree::read_json(aStream, aTree);
1133 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1134 int nViewId = aTree.get<int>("viewId");
1136 priv->m_aVisibleCursor = payloadToRectangle(pDocView, rRectangle.c_str());
1137 priv->m_bCursorOverlayVisible = true;
1138 if(nViewId == priv->m_nViewId)
1140 g_signal_emit(pDocView, doc_view_signals[CURSOR_CHANGED], 0,
1141 priv->m_aVisibleCursor.x,
1142 priv->m_aVisibleCursor.y,
1143 priv->m_aVisibleCursor.width,
1144 priv->m_aVisibleCursor.height);
1146 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1148 break;
1149 case LOK_CALLBACK_TEXT_SELECTION:
1151 priv->m_aTextSelectionRectangles = payloadToRectangles(pDocView, pCallback->m_aPayload.c_str());
1152 bool bIsTextSelected = !priv->m_aTextSelectionRectangles.empty();
1153 // In case the selection is empty, then we get no LOK_CALLBACK_TEXT_SELECTION_START/END events.
1154 if (!bIsTextSelected)
1156 memset(&priv->m_aTextSelectionStart, 0, sizeof(priv->m_aTextSelectionStart));
1157 memset(&priv->m_aHandleStartRect, 0, sizeof(priv->m_aHandleStartRect));
1158 memset(&priv->m_aTextSelectionEnd, 0, sizeof(priv->m_aTextSelectionEnd));
1159 memset(&priv->m_aHandleEndRect, 0, sizeof(priv->m_aHandleEndRect));
1161 else
1162 memset(&priv->m_aHandleMiddleRect, 0, sizeof(priv->m_aHandleMiddleRect));
1164 g_signal_emit(pDocView, doc_view_signals[TEXT_SELECTION], 0, bIsTextSelected);
1165 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1167 break;
1168 case LOK_CALLBACK_TEXT_SELECTION_START:
1170 priv->m_aTextSelectionStart = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1172 break;
1173 case LOK_CALLBACK_TEXT_SELECTION_END:
1175 priv->m_aTextSelectionEnd = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1177 break;
1178 case LOK_CALLBACK_CURSOR_VISIBLE:
1180 priv->m_bCursorVisible = pCallback->m_aPayload == "true";
1182 break;
1183 case LOK_CALLBACK_MOUSE_POINTER:
1185 // We do not want the cursor to get changed in view-only mode
1186 if (priv->m_bEdit)
1188 // The gtk docs claim that most css cursors should be supported, however
1189 // on my system at least this is not true and many cursors are unsupported.
1190 // In this case pCursor = null, which results in the default cursor
1191 // being set.
1192 GdkCursor* pCursor = gdk_cursor_new_from_name(gtk_widget_get_display(GTK_WIDGET(pDocView)),
1193 pCallback->m_aPayload.c_str());
1194 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(pDocView)), pCursor);
1197 break;
1198 case LOK_CALLBACK_GRAPHIC_SELECTION:
1200 if (pCallback->m_aPayload != "EMPTY")
1201 priv->m_aGraphicSelection = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1202 else
1203 memset(&priv->m_aGraphicSelection, 0, sizeof(priv->m_aGraphicSelection));
1204 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1206 break;
1207 case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
1209 std::stringstream aStream(pCallback->m_aPayload);
1210 boost::property_tree::ptree aTree;
1211 boost::property_tree::read_json(aStream, aTree);
1212 int nViewId = aTree.get<int>("viewId");
1213 int nPart = aTree.get<int>("part");
1214 const std::string& rRectangle = aTree.get<std::string>("selection");
1215 if (rRectangle != "EMPTY")
1216 priv->m_aGraphicViewSelections[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1217 else
1219 auto it = priv->m_aGraphicViewSelections.find(nViewId);
1220 if (it != priv->m_aGraphicViewSelections.end())
1221 priv->m_aGraphicViewSelections.erase(it);
1223 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1224 break;
1226 break;
1227 case LOK_CALLBACK_CELL_CURSOR:
1229 if (pCallback->m_aPayload != "EMPTY")
1230 priv->m_aCellCursor = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1231 else
1232 memset(&priv->m_aCellCursor, 0, sizeof(priv->m_aCellCursor));
1233 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1235 break;
1236 case LOK_CALLBACK_HYPERLINK_CLICKED:
1238 hyperlinkClicked(pDocView, pCallback->m_aPayload);
1240 break;
1241 case LOK_CALLBACK_STATE_CHANGED:
1243 commandChanged(pDocView, pCallback->m_aPayload);
1245 break;
1246 case LOK_CALLBACK_SEARCH_NOT_FOUND:
1248 searchNotFound(pDocView, pCallback->m_aPayload);
1250 break;
1251 case LOK_CALLBACK_DOCUMENT_SIZE_CHANGED:
1253 refreshSize(pDocView);
1254 g_signal_emit(pDocView, doc_view_signals[SIZE_CHANGED], 0, nullptr);
1256 break;
1257 case LOK_CALLBACK_SET_PART:
1259 setPart(pDocView, pCallback->m_aPayload);
1261 break;
1262 case LOK_CALLBACK_SEARCH_RESULT_SELECTION:
1264 boost::property_tree::ptree aTree;
1265 std::stringstream aStream(pCallback->m_aPayload);
1266 boost::property_tree::read_json(aStream, aTree);
1267 int nCount = aTree.get_child("searchResultSelection").size();
1268 searchResultCount(pDocView, std::to_string(nCount));
1270 break;
1271 case LOK_CALLBACK_UNO_COMMAND_RESULT:
1273 commandResult(pDocView, pCallback->m_aPayload);
1275 break;
1276 case LOK_CALLBACK_CELL_ADDRESS:
1278 addressChanged(pDocView, pCallback->m_aPayload);
1280 break;
1281 case LOK_CALLBACK_CELL_FORMULA:
1283 formulaChanged(pDocView, pCallback->m_aPayload);
1285 break;
1286 case LOK_CALLBACK_ERROR:
1288 reportError(pDocView, pCallback->m_aPayload);
1290 break;
1291 case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
1293 std::stringstream aStream(pCallback->m_aPayload);
1294 boost::property_tree::ptree aTree;
1295 boost::property_tree::read_json(aStream, aTree);
1296 int nViewId = aTree.get<int>("viewId");
1297 int nPart = aTree.get<int>("part");
1298 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1299 priv->m_aViewCursors[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1300 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1301 break;
1303 case LOK_CALLBACK_TEXT_VIEW_SELECTION:
1305 std::stringstream aStream(pCallback->m_aPayload);
1306 boost::property_tree::ptree aTree;
1307 boost::property_tree::read_json(aStream, aTree);
1308 int nViewId = aTree.get<int>("viewId");
1309 int nPart = aTree.get<int>("part");
1310 const std::string& rSelection = aTree.get<std::string>("selection");
1311 priv->m_aTextViewSelectionRectangles[nViewId] = ViewRectangles(nPart, payloadToRectangles(pDocView, rSelection.c_str()));
1312 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1313 break;
1315 case LOK_CALLBACK_VIEW_CURSOR_VISIBLE:
1317 std::stringstream aStream(pCallback->m_aPayload);
1318 boost::property_tree::ptree aTree;
1319 boost::property_tree::read_json(aStream, aTree);
1320 int nViewId = aTree.get<int>("viewId");
1321 const std::string& rVisible = aTree.get<std::string>("visible");
1322 priv->m_aViewCursorVisibilities[nViewId] = rVisible == "true";
1323 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1324 break;
1326 break;
1327 case LOK_CALLBACK_CELL_VIEW_CURSOR:
1329 std::stringstream aStream(pCallback->m_aPayload);
1330 boost::property_tree::ptree aTree;
1331 boost::property_tree::read_json(aStream, aTree);
1332 int nViewId = aTree.get<int>("viewId");
1333 int nPart = aTree.get<int>("part");
1334 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1335 if (rRectangle != "EMPTY")
1336 priv->m_aCellViewCursors[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1337 else
1339 auto it = priv->m_aCellViewCursors.find(nViewId);
1340 if (it != priv->m_aCellViewCursors.end())
1341 priv->m_aCellViewCursors.erase(it);
1343 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1344 break;
1346 case LOK_CALLBACK_VIEW_LOCK:
1348 std::stringstream aStream(pCallback->m_aPayload);
1349 boost::property_tree::ptree aTree;
1350 boost::property_tree::read_json(aStream, aTree);
1351 int nViewId = aTree.get<int>("viewId");
1352 int nPart = aTree.get<int>("part");
1353 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1354 if (rRectangle != "EMPTY")
1355 priv->m_aViewLockRectangles[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1356 else
1358 auto it = priv->m_aViewLockRectangles.find(nViewId);
1359 if (it != priv->m_aViewLockRectangles.end())
1360 priv->m_aViewLockRectangles.erase(it);
1362 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1363 break;
1365 case LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED:
1367 break;
1369 case LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED:
1371 break;
1373 case LOK_CALLBACK_COMMENT:
1374 g_signal_emit(pCallback->m_pDocView, doc_view_signals[COMMENT], 0, pCallback->m_aPayload.c_str());
1375 break;
1376 case LOK_CALLBACK_RULER_UPDATE:
1377 g_signal_emit(pCallback->m_pDocView, doc_view_signals[RULER], 0, pCallback->m_aPayload.c_str());
1378 break;
1379 case LOK_CALLBACK_WINDOW:
1380 g_signal_emit(pCallback->m_pDocView, doc_view_signals[WINDOW], 0, pCallback->m_aPayload.c_str());
1381 break;
1382 case LOK_CALLBACK_INVALIDATE_HEADER:
1383 g_signal_emit(pCallback->m_pDocView, doc_view_signals[INVALIDATE_HEADER], 0, pCallback->m_aPayload.c_str());
1384 break;
1385 case LOK_CALLBACK_REFERENCE_MARKS:
1387 std::stringstream aStream(pCallback->m_aPayload);
1388 boost::property_tree::ptree aTree;
1389 boost::property_tree::read_json(aStream, aTree);
1391 priv->m_aReferenceMarks.clear();
1393 for(const auto& rMark : aTree.get_child("marks"))
1395 sal_uInt32 nColor = std::stoi(rMark.second.get<std::string>("color"), nullptr, 16);
1396 std::string sRect = rMark.second.get<std::string>("rectangle");
1397 sal_uInt32 nPart = std::stoi(rMark.second.get<std::string>("part"));
1399 GdkRectangle aRect = payloadToRectangle(pDocView, sRect.c_str());
1400 priv->m_aReferenceMarks.push_back(std::pair<ViewRectangle, sal_uInt32>(ViewRectangle(nPart, aRect), nColor));
1403 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1404 break;
1407 case LOK_CALLBACK_CONTENT_CONTROL:
1409 std::stringstream aPayloadStream(pCallback->m_aPayload);
1410 boost::property_tree::ptree aTree;
1411 boost::property_tree::read_json(aPayloadStream, aTree);
1412 auto aAction = aTree.get<std::string>("action");
1413 if (aAction == "show")
1415 auto aRectangles = aTree.get<std::string>("rectangles");
1416 priv->m_aContentControlRectangles = payloadToRectangles(pDocView, aRectangles.c_str());
1418 auto it = aTree.find("alias");
1419 if (it == aTree.not_found())
1421 priv->m_aContentControlAlias.clear();
1423 else
1425 priv->m_aContentControlAlias = it->second.get_value<std::string>();
1428 else if (aAction == "hide")
1430 priv->m_aContentControlRectangles.clear();
1431 priv->m_aContentControlAlias.clear();
1433 else if (aAction == "change-picture")
1435 GtkWidget* pDialog = gtk_file_chooser_dialog_new(
1436 "Open File", GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))),
1437 GTK_FILE_CHOOSER_ACTION_OPEN, "Cancel", GTK_RESPONSE_CANCEL, "Open",
1438 GTK_RESPONSE_ACCEPT, nullptr);
1439 gint nRet = gtk_dialog_run(GTK_DIALOG(pDialog));
1440 if (nRet == GTK_RESPONSE_ACCEPT)
1442 GtkFileChooser* pChooser = GTK_FILE_CHOOSER(pDialog);
1443 char* pFilename = gtk_file_chooser_get_uri(pChooser);
1444 boost::property_tree::ptree aValues;
1445 aValues.put("type", "picture");
1446 aValues.put("changed", pFilename);
1447 std::stringstream aStream;
1448 boost::property_tree::write_json(aStream, aValues);
1449 std::string aJson = aStream.str();
1450 lok_doc_view_send_content_control_event(pDocView, aJson.c_str());
1452 g_free(pFilename);
1454 gtk_widget_destroy(pDialog);
1456 g_signal_emit(pCallback->m_pDocView, doc_view_signals[CONTENT_CONTROL], 0,
1457 pCallback->m_aPayload.c_str());
1458 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1460 break;
1462 case LOK_CALLBACK_STATUS_INDICATOR_START:
1463 case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
1464 case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
1465 case LOK_CALLBACK_DOCUMENT_PASSWORD:
1466 case LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY:
1467 case LOK_CALLBACK_VALIDITY_LIST_BUTTON:
1468 case LOK_CALLBACK_VALIDITY_INPUT_HELP:
1469 case LOK_CALLBACK_SIGNATURE_STATUS:
1470 case LOK_CALLBACK_CONTEXT_MENU:
1471 case LOK_CALLBACK_PROFILE_FRAME:
1472 case LOK_CALLBACK_CLIPBOARD_CHANGED:
1473 case LOK_CALLBACK_CONTEXT_CHANGED:
1474 case LOK_CALLBACK_CELL_SELECTION_AREA:
1475 case LOK_CALLBACK_CELL_AUTO_FILL_AREA:
1476 case LOK_CALLBACK_TABLE_SELECTED:
1477 case LOK_CALLBACK_JSDIALOG:
1478 case LOK_CALLBACK_CALC_FUNCTION_LIST:
1479 case LOK_CALLBACK_TAB_STOP_LIST:
1480 case LOK_CALLBACK_FORM_FIELD_BUTTON:
1481 case LOK_CALLBACK_INVALIDATE_SHEET_GEOMETRY:
1482 case LOK_CALLBACK_DOCUMENT_BACKGROUND_COLOR:
1483 case LOK_COMMAND_BLOCKED:
1484 case LOK_CALLBACK_SC_FOLLOW_JUMP:
1485 case LOK_CALLBACK_PRINT_RANGES:
1486 case LOK_CALLBACK_FONTS_MISSING:
1487 case LOK_CALLBACK_MEDIA_SHAPE:
1488 case LOK_CALLBACK_EXPORT_FILE:
1489 case LOK_CALLBACK_VIEW_RENDER_STATE:
1490 case LOK_CALLBACK_APPLICATION_BACKGROUND_COLOR:
1491 case LOK_CALLBACK_A11Y_FOCUS_CHANGED:
1492 case LOK_CALLBACK_A11Y_CARET_CHANGED:
1493 case LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED:
1494 case LOK_CALLBACK_COLOR_PALETTES:
1496 // TODO: Implement me
1497 break;
1500 delete pCallback;
1502 return G_SOURCE_REMOVE;
1505 static void callbackWorker (int nType, const char* pPayload, void* pData)
1507 LOKDocView* pDocView = LOK_DOC_VIEW (pData);
1509 CallbackData* pCallback = new CallbackData(nType, pPayload ? pPayload : "(nil)", pDocView);
1510 LOKDocViewPrivate& priv = getPrivate(pDocView);
1511 std::stringstream ss;
1512 ss << "callbackWorker, view #" << priv->m_nViewId << ": " << lokCallbackTypeToString(nType) << ", '" << (pPayload ? pPayload : "(nil)") << "'";
1513 g_info("%s", ss.str().c_str());
1514 gdk_threads_add_idle(callback, pCallback);
1517 static void
1518 renderHandle(LOKDocView* pDocView,
1519 cairo_t* pCairo,
1520 const GdkRectangle& rCursor,
1521 cairo_surface_t* pHandle,
1522 GdkRectangle& rRectangle)
1524 LOKDocViewPrivate& priv = getPrivate(pDocView);
1525 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
1526 GdkPoint aCursorBottom;
1527 int nHandleWidth, nHandleHeight;
1528 double fHandleScale;
1530 nHandleWidth = cairo_image_surface_get_width(pHandle);
1531 nHandleHeight = cairo_image_surface_get_height(pHandle);
1532 // We want to scale down the handle, so that its height is the same as the cursor caret.
1533 fHandleScale = twipToPixel(rCursor.height, priv->m_fZoom) / nHandleHeight;
1534 // We want the top center of the handle bitmap to be at the bottom center of the cursor rectangle.
1535 aCursorBottom.x = twipToPixel(rCursor.x, priv->m_fZoom) + twipToPixel(rCursor.width, priv->m_fZoom) / 2 - (nHandleWidth * fHandleScale) / 2;
1536 aCursorBottom.y = twipToPixel(rCursor.y, priv->m_fZoom) + twipToPixel(rCursor.height, priv->m_fZoom);
1538 cairo_save (pCairo);
1539 cairo_scale(pCairo, 1.0 / nScaleFactor, 1.0 / nScaleFactor);
1540 cairo_translate(pCairo, aCursorBottom.x * nScaleFactor, aCursorBottom.y * nScaleFactor);
1541 cairo_scale(pCairo, fHandleScale * nScaleFactor, fHandleScale * nScaleFactor);
1542 cairo_set_source_surface(pCairo, pHandle, 0, 0);
1543 cairo_paint(pCairo);
1544 cairo_restore (pCairo);
1546 rRectangle.x = aCursorBottom.x;
1547 rRectangle.y = aCursorBottom.y;
1548 rRectangle.width = nHandleWidth * fHandleScale;
1549 rRectangle.height = nHandleHeight * fHandleScale;
1552 /// Renders handles around an rSelection rectangle on pCairo.
1553 static void
1554 renderGraphicHandle(LOKDocView* pDocView,
1555 cairo_t* pCairo,
1556 const GdkRectangle& rSelection,
1557 const GdkRGBA& rColor)
1559 LOKDocViewPrivate& priv = getPrivate(pDocView);
1560 int nHandleWidth = 9, nHandleHeight = 9;
1561 GdkRectangle aSelection;
1563 aSelection.x = twipToPixel(rSelection.x, priv->m_fZoom);
1564 aSelection.y = twipToPixel(rSelection.y, priv->m_fZoom);
1565 aSelection.width = twipToPixel(rSelection.width, priv->m_fZoom);
1566 aSelection.height = twipToPixel(rSelection.height, priv->m_fZoom);
1568 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
1570 int x = aSelection.x, y = aSelection.y;
1572 switch (i)
1574 case 0: // top-left
1575 break;
1576 case 1: // top-middle
1577 x += aSelection.width / 2;
1578 break;
1579 case 2: // top-right
1580 x += aSelection.width;
1581 break;
1582 case 3: // middle-left
1583 y += aSelection.height / 2;
1584 break;
1585 case 4: // middle-right
1586 x += aSelection.width;
1587 y += aSelection.height / 2;
1588 break;
1589 case 5: // bottom-left
1590 y += aSelection.height;
1591 break;
1592 case 6: // bottom-middle
1593 x += aSelection.width / 2;
1594 y += aSelection.height;
1595 break;
1596 case 7: // bottom-right
1597 x += aSelection.width;
1598 y += aSelection.height;
1599 break;
1602 // Center the handle.
1603 x -= nHandleWidth / 2;
1604 y -= nHandleHeight / 2;
1606 priv->m_aGraphicHandleRects[i].x = x;
1607 priv->m_aGraphicHandleRects[i].y = y;
1608 priv->m_aGraphicHandleRects[i].width = nHandleWidth;
1609 priv->m_aGraphicHandleRects[i].height = nHandleHeight;
1611 cairo_set_source_rgb(pCairo, rColor.red, rColor.green, rColor.blue);
1612 cairo_rectangle(pCairo, x, y, nHandleWidth, nHandleHeight);
1613 cairo_fill(pCairo);
1617 /// Finishes the paint tile operation and returns the result, if any
1618 static gpointer
1619 paintTileFinish(LOKDocView* pDocView, GAsyncResult* res, GError **error)
1621 GTask* task = G_TASK(res);
1623 g_return_val_if_fail(LOK_IS_DOC_VIEW(pDocView), nullptr);
1624 g_return_val_if_fail(g_task_is_valid(res, pDocView), nullptr);
1625 g_return_val_if_fail(error == nullptr || *error == nullptr, nullptr);
1627 return g_task_propagate_pointer(task, error);
1630 /// Callback called in the main UI thread when paintTileInThread in LOK thread has finished
1631 static void
1632 paintTileCallback(GObject* sourceObject, GAsyncResult* res, gpointer userData)
1634 LOKDocView* pDocView = LOK_DOC_VIEW(sourceObject);
1635 LOKDocViewPrivate& priv = getPrivate(pDocView);
1636 LOEvent* pLOEvent = static_cast<LOEvent*>(userData);
1637 std::unique_ptr<TileBuffer>& buffer = priv->m_pTileBuffer;
1638 GError* error;
1640 error = nullptr;
1641 cairo_surface_t* pSurface = static_cast<cairo_surface_t*>(paintTileFinish(pDocView, res, &error));
1642 if (error != nullptr)
1644 if (error->domain == LOK_TILEBUFFER_ERROR &&
1645 error->code == LOK_TILEBUFFER_CHANGED)
1646 g_info("Skipping paint tile request because corresponding"
1647 "tile buffer has been destroyed");
1648 else
1649 g_warning("Unable to get painted GdkPixbuf: %s", error->message);
1650 g_error_free(error);
1651 return;
1654 buffer->setTile(pLOEvent->m_nPaintTileX, pLOEvent->m_nPaintTileY, pSurface);
1655 gdk_threads_add_idle(queueDraw, GTK_WIDGET(pDocView));
1657 cairo_surface_destroy(pSurface);
1661 static bool
1662 renderDocument(LOKDocView* pDocView, cairo_t* pCairo)
1664 LOKDocViewPrivate& priv = getPrivate(pDocView);
1665 GdkRectangle aVisibleArea;
1666 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
1667 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
1668 long nDocumentWidthPixels = twipToPixel(priv->m_nDocumentWidthTwips, priv->m_fZoom) * nScaleFactor;
1669 long nDocumentHeightPixels = twipToPixel(priv->m_nDocumentHeightTwips, priv->m_fZoom) * nScaleFactor;
1670 // Total number of rows / columns in this document.
1671 guint nRows = ceil(static_cast<double>(nDocumentHeightPixels) / nTileSizePixelsScaled);
1672 guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixelsScaled);
1674 cairo_save (pCairo);
1675 cairo_scale (pCairo, 1.0/nScaleFactor, 1.0/nScaleFactor);
1676 gdk_cairo_get_clip_rectangle (pCairo, &aVisibleArea);
1677 aVisibleArea.x = pixelToTwip (aVisibleArea.x, priv->m_fZoom);
1678 aVisibleArea.y = pixelToTwip (aVisibleArea.y, priv->m_fZoom);
1679 aVisibleArea.width = pixelToTwip (aVisibleArea.width, priv->m_fZoom);
1680 aVisibleArea.height = pixelToTwip (aVisibleArea.height, priv->m_fZoom);
1682 // Render the tiles.
1683 for (guint nRow = 0; nRow < nRows; ++nRow)
1685 for (guint nColumn = 0; nColumn < nColumns; ++nColumn)
1687 GdkRectangle aTileRectangleTwips, aTileRectanglePixels;
1688 bool bPaint = true;
1690 // Determine size of the tile: the rightmost/bottommost tiles may
1691 // be smaller, and we need the size to decide if we need to repaint.
1692 if (nColumn == nColumns - 1)
1693 aTileRectanglePixels.width = nDocumentWidthPixels - nColumn * nTileSizePixelsScaled;
1694 else
1695 aTileRectanglePixels.width = nTileSizePixelsScaled;
1696 if (nRow == nRows - 1)
1697 aTileRectanglePixels.height = nDocumentHeightPixels - nRow * nTileSizePixelsScaled;
1698 else
1699 aTileRectanglePixels.height = nTileSizePixelsScaled;
1701 // Determine size and position of the tile in document coordinates,
1702 // so we can decide if we can skip painting for partial rendering.
1703 aTileRectangleTwips.x = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom) * nColumn;
1704 aTileRectangleTwips.y = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom) * nRow;
1705 aTileRectangleTwips.width = pixelToTwip(aTileRectanglePixels.width, priv->m_fZoom);
1706 aTileRectangleTwips.height = pixelToTwip(aTileRectanglePixels.height, priv->m_fZoom);
1708 if (!gdk_rectangle_intersect(&aVisibleArea, &aTileRectangleTwips, nullptr))
1709 bPaint = false;
1711 if (bPaint)
1713 LOEvent* pLOEvent = new LOEvent(LOK_PAINT_TILE);
1714 pLOEvent->m_nPaintTileX = nRow;
1715 pLOEvent->m_nPaintTileY = nColumn;
1716 pLOEvent->m_fPaintTileZoom = priv->m_fZoom;
1717 pLOEvent->m_pTileBuffer = &*priv->m_pTileBuffer;
1718 GTask* task = g_task_new(pDocView, nullptr, paintTileCallback, pLOEvent);
1719 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
1721 Tile& currentTile = priv->m_pTileBuffer->getTile(nRow, nColumn, task, priv->lokThreadPool);
1722 cairo_surface_t* pSurface = currentTile.getBuffer();
1723 cairo_set_source_surface(pCairo, pSurface,
1724 twipToPixel(aTileRectangleTwips.x, priv->m_fZoom),
1725 twipToPixel(aTileRectangleTwips.y, priv->m_fZoom));
1726 cairo_paint(pCairo);
1727 g_object_unref(task);
1732 cairo_restore (pCairo);
1733 return false;
1736 static const GdkRGBA& getDarkColor(int nViewId, LOKDocViewPrivate& priv)
1738 static std::map<int, GdkRGBA> aColorMap;
1739 auto it = aColorMap.find(nViewId);
1740 if (it != aColorMap.end())
1741 return it->second;
1743 if (priv->m_eDocumentType == LOK_DOCTYPE_TEXT)
1745 char* pValues = priv->m_pDocument->pClass->getCommandValues(priv->m_pDocument, ".uno:TrackedChangeAuthors");
1746 std::stringstream aInfo;
1747 aInfo << "lok::Document::getCommandValues('.uno:TrackedChangeAuthors') returned '" << pValues << "'" << std::endl;
1748 g_info("%s", aInfo.str().c_str());
1750 std::stringstream aStream(pValues);
1751 boost::property_tree::ptree aTree;
1752 boost::property_tree::read_json(aStream, aTree);
1753 for (const auto& rValue : aTree.get_child("authors"))
1755 const std::string& rName = rValue.second.get<std::string>("name");
1756 guint32 nColor = rValue.second.get<guint32>("color");
1757 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};
1758 auto itAuthorViews = g_aAuthorViews.find(rName);
1759 if (itAuthorViews != g_aAuthorViews.end())
1760 aColorMap[itAuthorViews->second] = aColor;
1763 else
1765 // Based on tools/color.hxx, COL_AUTHOR1_DARK..COL_AUTHOR9_DARK.
1766 static std::vector<GdkRGBA> aColors =
1768 {(double(198))/255, (double(146))/255, (double(0))/255, 0},
1769 {(double(6))/255, (double(70))/255, (double(162))/255, 0},
1770 {(double(87))/255, (double(157))/255, (double(28))/255, 0},
1771 {(double(105))/255, (double(43))/255, (double(157))/255, 0},
1772 {(double(197))/255, (double(0))/255, (double(11))/255, 0},
1773 {(double(0))/255, (double(128))/255, (double(128))/255, 0},
1774 {(double(140))/255, (double(132))/255, (double(0))/255, 0},
1775 {(double(43))/255, (double(85))/255, (double(107))/255, 0},
1776 {(double(209))/255, (double(118))/255, (double(0))/255, 0},
1778 static int nColorCounter = 0;
1779 GdkRGBA aColor = aColors[nColorCounter++ % aColors.size()];
1780 aColorMap[nViewId] = aColor;
1782 assert(aColorMap.find(nViewId) != aColorMap.end());
1783 return aColorMap[nViewId];
1786 static bool
1787 renderOverlay(LOKDocView* pDocView, cairo_t* pCairo)
1789 LOKDocViewPrivate& priv = getPrivate(pDocView);
1791 if (priv->m_bEdit && priv->m_bCursorVisible && priv->m_bCursorOverlayVisible && !isEmptyRectangle(priv->m_aVisibleCursor))
1793 if (priv->m_aVisibleCursor.width < 30)
1794 // Set a minimal width if it would be 0.
1795 priv->m_aVisibleCursor.width = 30;
1797 cairo_set_source_rgb(pCairo, 0, 0, 0);
1798 cairo_rectangle(pCairo,
1799 twipToPixel(priv->m_aVisibleCursor.x, priv->m_fZoom),
1800 twipToPixel(priv->m_aVisibleCursor.y, priv->m_fZoom),
1801 twipToPixel(priv->m_aVisibleCursor.width, priv->m_fZoom),
1802 twipToPixel(priv->m_aVisibleCursor.height, priv->m_fZoom));
1803 cairo_fill(pCairo);
1806 // View cursors: they do not blink and are colored.
1807 if (priv->m_bEdit && !priv->m_aViewCursors.empty())
1809 for (auto& rPair : priv->m_aViewCursors)
1811 auto itVisibility = priv->m_aViewCursorVisibilities.find(rPair.first);
1812 if (itVisibility != priv->m_aViewCursorVisibilities.end() && !itVisibility->second)
1813 continue;
1815 // Show view cursors when in Writer or when the part matches.
1816 if (rPair.second.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
1817 continue;
1819 GdkRectangle& rCursor = rPair.second.m_aRectangle;
1820 if (rCursor.width < 30)
1821 // Set a minimal width if it would be 0.
1822 rCursor.width = 30;
1824 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1825 cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
1826 cairo_rectangle(pCairo,
1827 twipToPixel(rCursor.x, priv->m_fZoom),
1828 twipToPixel(rCursor.y, priv->m_fZoom),
1829 twipToPixel(rCursor.width, priv->m_fZoom),
1830 twipToPixel(rCursor.height, priv->m_fZoom));
1831 cairo_fill(pCairo);
1835 if (priv->m_bEdit && priv->m_bCursorVisible && !isEmptyRectangle(priv->m_aVisibleCursor) && priv->m_aTextSelectionRectangles.empty())
1837 // Have a cursor, but no selection: we need the middle handle.
1838 gchar* handleMiddlePath = g_strconcat (priv->m_aLOPath.c_str(), CURSOR_HANDLE_DIR, "handle_image_middle.png", nullptr);
1839 if (!priv->m_pHandleMiddle)
1841 priv->m_pHandleMiddle = cairo_image_surface_create_from_png(handleMiddlePath);
1842 assert(cairo_surface_status(priv->m_pHandleMiddle) == CAIRO_STATUS_SUCCESS);
1844 g_free (handleMiddlePath);
1845 renderHandle(pDocView, pCairo, priv->m_aVisibleCursor, priv->m_pHandleMiddle, priv->m_aHandleMiddleRect);
1848 if (!priv->m_aTextSelectionRectangles.empty())
1850 for (const GdkRectangle& rRectangle : priv->m_aTextSelectionRectangles)
1852 // Blue with 75% transparency.
1853 cairo_set_source_rgba(pCairo, (double(0x43))/255, (double(0xac))/255, (double(0xe8))/255, 0.25);
1854 cairo_rectangle(pCairo,
1855 twipToPixel(rRectangle.x, priv->m_fZoom),
1856 twipToPixel(rRectangle.y, priv->m_fZoom),
1857 twipToPixel(rRectangle.width, priv->m_fZoom),
1858 twipToPixel(rRectangle.height, priv->m_fZoom));
1859 cairo_fill(pCairo);
1862 // Handles
1863 if (!isEmptyRectangle(priv->m_aTextSelectionStart))
1865 // Have a start position: we need a start handle.
1866 gchar* handleStartPath = g_strconcat (priv->m_aLOPath.c_str(), CURSOR_HANDLE_DIR, "handle_image_start.png", nullptr);
1867 if (!priv->m_pHandleStart)
1869 priv->m_pHandleStart = cairo_image_surface_create_from_png(handleStartPath);
1870 assert(cairo_surface_status(priv->m_pHandleStart) == CAIRO_STATUS_SUCCESS);
1872 renderHandle(pDocView, pCairo, priv->m_aTextSelectionStart, priv->m_pHandleStart, priv->m_aHandleStartRect);
1873 g_free (handleStartPath);
1875 if (!isEmptyRectangle(priv->m_aTextSelectionEnd))
1877 // Have a start position: we need an end handle.
1878 gchar* handleEndPath = g_strconcat (priv->m_aLOPath.c_str(), CURSOR_HANDLE_DIR, "handle_image_end.png", nullptr);
1879 if (!priv->m_pHandleEnd)
1881 priv->m_pHandleEnd = cairo_image_surface_create_from_png(handleEndPath);
1882 assert(cairo_surface_status(priv->m_pHandleEnd) == CAIRO_STATUS_SUCCESS);
1884 renderHandle(pDocView, pCairo, priv->m_aTextSelectionEnd, priv->m_pHandleEnd, priv->m_aHandleEndRect);
1885 g_free (handleEndPath);
1889 if (!priv->m_aContentControlRectangles.empty())
1891 for (const GdkRectangle& rRectangle : priv->m_aContentControlRectangles)
1893 // Black with 75% transparency.
1894 cairo_set_source_rgba(pCairo, (double(0x7f))/255, (double(0x7f))/255, (double(0x7f))/255, 0.25);
1895 cairo_rectangle(pCairo,
1896 twipToPixel(rRectangle.x, priv->m_fZoom),
1897 twipToPixel(rRectangle.y, priv->m_fZoom),
1898 twipToPixel(rRectangle.width, priv->m_fZoom),
1899 twipToPixel(rRectangle.height, priv->m_fZoom));
1900 cairo_fill(pCairo);
1903 if (!priv->m_aContentControlAlias.empty())
1905 cairo_text_extents_t aExtents;
1906 cairo_text_extents(pCairo, priv->m_aContentControlAlias.c_str(), &aExtents);
1907 // Blue with 75% transparency.
1908 cairo_set_source_rgba(pCairo, 0, 0, 1, 0.25);
1909 cairo_rectangle(pCairo,
1910 twipToPixel(priv->m_aContentControlRectangles[0].x, priv->m_fZoom) + aExtents.x_bearing,
1911 twipToPixel(priv->m_aContentControlRectangles[0].y, priv->m_fZoom) + aExtents.y_bearing,
1912 aExtents.width,
1913 aExtents.height);
1914 cairo_fill(pCairo);
1916 cairo_move_to(pCairo,
1917 twipToPixel(priv->m_aContentControlRectangles[0].x, priv->m_fZoom),
1918 twipToPixel(priv->m_aContentControlRectangles[0].y, priv->m_fZoom));
1919 cairo_set_source_rgb(pCairo, 0, 0, 0);
1920 cairo_show_text(pCairo, priv->m_aContentControlAlias.c_str());
1921 cairo_fill(pCairo);
1925 // Selections of other views.
1926 for (const auto& rPair : priv->m_aTextViewSelectionRectangles)
1928 if (rPair.second.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
1929 continue;
1931 for (const GdkRectangle& rRectangle : rPair.second.m_aRectangles)
1933 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1934 // 75% transparency.
1935 cairo_set_source_rgba(pCairo, rDark.red, rDark.green, rDark.blue, 0.25);
1936 cairo_rectangle(pCairo,
1937 twipToPixel(rRectangle.x, priv->m_fZoom),
1938 twipToPixel(rRectangle.y, priv->m_fZoom),
1939 twipToPixel(rRectangle.width, priv->m_fZoom),
1940 twipToPixel(rRectangle.height, priv->m_fZoom));
1941 cairo_fill(pCairo);
1945 if (!isEmptyRectangle(priv->m_aGraphicSelection))
1947 GdkRGBA const aBlack{0, 0, 0, 0};
1948 renderGraphicHandle(pDocView, pCairo, priv->m_aGraphicSelection, aBlack);
1951 // Graphic selections of other views.
1952 for (const auto& rPair : priv->m_aGraphicViewSelections)
1954 const ViewRectangle& rRectangle = rPair.second;
1955 if (rRectangle.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
1956 continue;
1958 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1959 renderGraphicHandle(pDocView, pCairo, rRectangle.m_aRectangle, rDark);
1962 // Draw the cell cursor.
1963 if (!isEmptyRectangle(priv->m_aCellCursor))
1965 cairo_set_source_rgb(pCairo, 0, 0, 0);
1966 cairo_rectangle(pCairo,
1967 twipToPixel(priv->m_aCellCursor.x, priv->m_fZoom),
1968 twipToPixel(priv->m_aCellCursor.y, priv->m_fZoom),
1969 twipToPixel(priv->m_aCellCursor.width, priv->m_fZoom),
1970 twipToPixel(priv->m_aCellCursor.height, priv->m_fZoom));
1971 cairo_set_line_width(pCairo, 2.0);
1972 cairo_stroke(pCairo);
1975 // Cell view cursors: they are colored.
1976 for (const auto& rPair : priv->m_aCellViewCursors)
1978 const ViewRectangle& rCursor = rPair.second;
1979 if (rCursor.m_nPart != priv->m_nPartId)
1980 continue;
1982 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1983 cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
1984 cairo_rectangle(pCairo,
1985 twipToPixel(rCursor.m_aRectangle.x, priv->m_fZoom),
1986 twipToPixel(rCursor.m_aRectangle.y, priv->m_fZoom),
1987 twipToPixel(rCursor.m_aRectangle.width, priv->m_fZoom),
1988 twipToPixel(rCursor.m_aRectangle.height, priv->m_fZoom));
1989 cairo_set_line_width(pCairo, 2.0);
1990 cairo_stroke(pCairo);
1993 // Draw reference marks.
1994 for (const auto& rPair : priv->m_aReferenceMarks)
1996 const ViewRectangle& rMark = rPair.first;
1997 if (rMark.m_nPart != priv->m_nPartId)
1998 continue;
2000 sal_uInt32 nColor = rPair.second;
2001 sal_uInt8 nRed = (nColor >> 16) & 0xff;
2002 sal_uInt8 nGreen = (nColor >> 8) & 0xff;
2003 sal_uInt8 nBlue = nColor & 0xff;
2004 cairo_set_source_rgb(pCairo, nRed, nGreen, nBlue);
2005 cairo_rectangle(pCairo,
2006 twipToPixel(rMark.m_aRectangle.x, priv->m_fZoom),
2007 twipToPixel(rMark.m_aRectangle.y, priv->m_fZoom),
2008 twipToPixel(rMark.m_aRectangle.width, priv->m_fZoom),
2009 twipToPixel(rMark.m_aRectangle.height, priv->m_fZoom));
2010 cairo_set_line_width(pCairo, 2.0);
2011 cairo_stroke(pCairo);
2014 // View locks: they are colored.
2015 for (const auto& rPair : priv->m_aViewLockRectangles)
2017 const ViewRectangle& rRectangle = rPair.second;
2018 if (rRectangle.m_nPart != priv->m_nPartId)
2019 continue;
2021 // Draw a rectangle.
2022 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
2023 cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
2024 cairo_rectangle(pCairo,
2025 twipToPixel(rRectangle.m_aRectangle.x, priv->m_fZoom),
2026 twipToPixel(rRectangle.m_aRectangle.y, priv->m_fZoom),
2027 twipToPixel(rRectangle.m_aRectangle.width, priv->m_fZoom),
2028 twipToPixel(rRectangle.m_aRectangle.height, priv->m_fZoom));
2029 cairo_set_line_width(pCairo, 2.0);
2030 cairo_stroke(pCairo);
2032 // And a lock.
2033 cairo_rectangle(pCairo,
2034 twipToPixel(rRectangle.m_aRectangle.x + rRectangle.m_aRectangle.width, priv->m_fZoom) - 25,
2035 twipToPixel(rRectangle.m_aRectangle.y + rRectangle.m_aRectangle.height, priv->m_fZoom) - 15,
2037 10);
2038 cairo_fill(pCairo);
2039 cairo_arc(pCairo,
2040 twipToPixel(rRectangle.m_aRectangle.x + rRectangle.m_aRectangle.width, priv->m_fZoom) - 15,
2041 twipToPixel(rRectangle.m_aRectangle.y + rRectangle.m_aRectangle.height, priv->m_fZoom) - 15,
2043 M_PI,
2044 2 * M_PI);
2045 cairo_stroke(pCairo);
2048 return false;
2051 static gboolean
2052 lok_doc_view_signal_button(GtkWidget* pWidget, GdkEventButton* pEvent)
2054 LOKDocView* pDocView = LOK_DOC_VIEW (pWidget);
2055 LOKDocViewPrivate& priv = getPrivate(pDocView);
2056 GError* error = nullptr;
2058 g_info("LOKDocView_Impl::signalButton: %d, %d (in twips: %d, %d)",
2059 static_cast<int>(pEvent->x), static_cast<int>(pEvent->y),
2060 static_cast<int>(pixelToTwip(pEvent->x, priv->m_fZoom)),
2061 static_cast<int>(pixelToTwip(pEvent->y, priv->m_fZoom)));
2062 gtk_widget_grab_focus(GTK_WIDGET(pDocView));
2064 switch (pEvent->type)
2066 case GDK_BUTTON_PRESS:
2068 GdkRectangle aClick;
2069 aClick.x = pEvent->x;
2070 aClick.y = pEvent->y;
2071 aClick.width = 1;
2072 aClick.height = 1;
2074 if (handleTextSelectionOnButtonPress(aClick, pDocView))
2075 return FALSE;
2076 if (handleGraphicSelectionOnButtonPress(aClick, pDocView))
2077 return FALSE;
2079 int nCount = 1;
2080 if ((pEvent->time - priv->m_nLastButtonPressTime) < 250)
2081 nCount++;
2082 priv->m_nLastButtonPressTime = pEvent->time;
2083 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2084 LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
2085 pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEBUTTONDOWN;
2086 pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
2087 pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
2088 pLOEvent->m_nPostMouseEventCount = nCount;
2089 switch (pEvent->button)
2091 case 1:
2092 pLOEvent->m_nPostMouseEventButton = MOUSE_LEFT;
2093 break;
2094 case 2:
2095 pLOEvent->m_nPostMouseEventButton = MOUSE_MIDDLE;
2096 break;
2097 case 3:
2098 pLOEvent->m_nPostMouseEventButton = MOUSE_RIGHT;
2099 break;
2101 pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
2102 priv->m_nLastButtonPressed = pLOEvent->m_nPostMouseEventButton;
2103 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2105 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2106 if (error != nullptr)
2108 g_warning("Unable to call LOK_POST_MOUSE_EVENT: %s", error->message);
2109 g_clear_error(&error);
2111 g_object_unref(task);
2112 break;
2114 case GDK_BUTTON_RELEASE:
2116 if (handleTextSelectionOnButtonRelease(pDocView))
2117 return FALSE;
2118 if (handleGraphicSelectionOnButtonRelease(pDocView, pEvent))
2119 return FALSE;
2121 int nCount = 1;
2122 if ((pEvent->time - priv->m_nLastButtonReleaseTime) < 250)
2123 nCount++;
2124 priv->m_nLastButtonReleaseTime = pEvent->time;
2125 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2126 LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
2127 pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEBUTTONUP;
2128 pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
2129 pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
2130 pLOEvent->m_nPostMouseEventCount = nCount;
2131 switch (pEvent->button)
2133 case 1:
2134 pLOEvent->m_nPostMouseEventButton = MOUSE_LEFT;
2135 break;
2136 case 2:
2137 pLOEvent->m_nPostMouseEventButton = MOUSE_MIDDLE;
2138 break;
2139 case 3:
2140 pLOEvent->m_nPostMouseEventButton = MOUSE_RIGHT;
2141 break;
2143 pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
2144 priv->m_nLastButtonPressed = pLOEvent->m_nPostMouseEventButton;
2145 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2147 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2148 if (error != nullptr)
2150 g_warning("Unable to call LOK_POST_MOUSE_EVENT: %s", error->message);
2151 g_clear_error(&error);
2153 g_object_unref(task);
2154 break;
2156 default:
2157 break;
2159 return FALSE;
2162 static void
2163 getDragPoint(GdkRectangle* pHandle,
2164 GdkEventMotion* pEvent,
2165 GdkPoint* pPoint)
2167 GdkPoint aCursor, aHandle;
2169 // Center of the cursor rectangle: we know that it's above the handle.
2170 aCursor.x = pHandle->x + pHandle->width / 2;
2171 aCursor.y = pHandle->y - pHandle->height / 2;
2172 // Center of the handle rectangle.
2173 aHandle.x = pHandle->x + pHandle->width / 2;
2174 aHandle.y = pHandle->y + pHandle->height / 2;
2175 // Our target is the original cursor position + the dragged offset.
2176 pPoint->x = aCursor.x + (pEvent->x - aHandle.x);
2177 pPoint->y = aCursor.y + (pEvent->y - aHandle.y);
2180 static gboolean
2181 lok_doc_view_signal_motion (GtkWidget* pWidget, GdkEventMotion* pEvent)
2183 LOKDocView* pDocView = LOK_DOC_VIEW (pWidget);
2184 LOKDocViewPrivate& priv = getPrivate(pDocView);
2185 GdkPoint aPoint;
2186 GError* error = nullptr;
2188 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2189 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2190 if (priv->m_bInDragMiddleHandle)
2192 g_info("lcl_signalMotion: dragging the middle handle");
2193 getDragPoint(&priv->m_aHandleMiddleRect, pEvent, &aPoint);
2194 priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_RESET, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
2195 return FALSE;
2197 if (priv->m_bInDragStartHandle)
2199 g_info("lcl_signalMotion: dragging the start handle");
2200 getDragPoint(&priv->m_aHandleStartRect, pEvent, &aPoint);
2201 priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_START, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
2202 return FALSE;
2204 if (priv->m_bInDragEndHandle)
2206 g_info("lcl_signalMotion: dragging the end handle");
2207 getDragPoint(&priv->m_aHandleEndRect, pEvent, &aPoint);
2208 priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_END, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
2209 return FALSE;
2211 aGuard.unlock();
2212 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
2214 if (priv->m_bInDragGraphicHandles[i])
2216 g_info("lcl_signalMotion: dragging the graphic handle #%d", i);
2217 return FALSE;
2220 if (priv->m_bInDragGraphicSelection)
2222 g_info("lcl_signalMotion: dragging the graphic selection");
2223 return FALSE;
2226 GdkRectangle aMotionInTwipsInTwips;
2227 aMotionInTwipsInTwips.x = pixelToTwip(pEvent->x, priv->m_fZoom);
2228 aMotionInTwipsInTwips.y = pixelToTwip(pEvent->y, priv->m_fZoom);
2229 aMotionInTwipsInTwips.width = 1;
2230 aMotionInTwipsInTwips.height = 1;
2231 if (gdk_rectangle_intersect(&aMotionInTwipsInTwips, &priv->m_aGraphicSelection, nullptr))
2233 g_info("lcl_signalMotion: start of drag graphic selection");
2234 priv->m_bInDragGraphicSelection = true;
2236 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2237 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
2238 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_START;
2239 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(pEvent->x, priv->m_fZoom);
2240 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(pEvent->y, priv->m_fZoom);
2241 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2243 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2244 if (error != nullptr)
2246 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
2247 g_clear_error(&error);
2249 g_object_unref(task);
2251 return FALSE;
2254 // Otherwise a mouse move, as on the desktop.
2256 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2257 LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
2258 pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEMOVE;
2259 pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
2260 pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
2261 pLOEvent->m_nPostMouseEventCount = 1;
2262 pLOEvent->m_nPostMouseEventButton = priv->m_nLastButtonPressed;
2263 pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
2265 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2267 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2268 if (error != nullptr)
2270 g_warning("Unable to call LOK_MOUSEEVENT_MOUSEMOVE: %s", error->message);
2271 g_clear_error(&error);
2273 g_object_unref(task);
2275 return FALSE;
2278 static void
2279 setGraphicSelectionInThread(gpointer data)
2281 GTask* task = G_TASK(data);
2282 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2283 LOKDocViewPrivate& priv = getPrivate(pDocView);
2284 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2286 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2287 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2288 std::stringstream ss;
2289 ss << "lok::Document::setGraphicSelection(" << pLOEvent->m_nSetGraphicSelectionType;
2290 ss << ", " << pLOEvent->m_nSetGraphicSelectionX;
2291 ss << ", " << pLOEvent->m_nSetGraphicSelectionY << ")";
2292 g_info("%s", ss.str().c_str());
2293 priv->m_pDocument->pClass->setGraphicSelection(priv->m_pDocument,
2294 pLOEvent->m_nSetGraphicSelectionType,
2295 pLOEvent->m_nSetGraphicSelectionX,
2296 pLOEvent->m_nSetGraphicSelectionY);
2299 static void
2300 setClientZoomInThread(gpointer data)
2302 GTask* task = G_TASK(data);
2303 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2304 LOKDocViewPrivate& priv = getPrivate(pDocView);
2305 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2307 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2308 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2309 priv->m_pDocument->pClass->setClientZoom(priv->m_pDocument,
2310 pLOEvent->m_nTilePixelWidth,
2311 pLOEvent->m_nTilePixelHeight,
2312 pLOEvent->m_nTileTwipWidth,
2313 pLOEvent->m_nTileTwipHeight);
2316 static void
2317 postMouseEventInThread(gpointer data)
2319 GTask* task = G_TASK(data);
2320 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2321 LOKDocViewPrivate& priv = getPrivate(pDocView);
2322 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2324 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2325 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2326 std::stringstream ss;
2327 ss << "lok::Document::postMouseEvent(" << pLOEvent->m_nPostMouseEventType;
2328 ss << ", " << pLOEvent->m_nPostMouseEventX;
2329 ss << ", " << pLOEvent->m_nPostMouseEventY;
2330 ss << ", " << pLOEvent->m_nPostMouseEventCount;
2331 ss << ", " << pLOEvent->m_nPostMouseEventButton;
2332 ss << ", " << pLOEvent->m_nPostMouseEventModifier << ")";
2333 g_info("%s", ss.str().c_str());
2334 priv->m_pDocument->pClass->postMouseEvent(priv->m_pDocument,
2335 pLOEvent->m_nPostMouseEventType,
2336 pLOEvent->m_nPostMouseEventX,
2337 pLOEvent->m_nPostMouseEventY,
2338 pLOEvent->m_nPostMouseEventCount,
2339 pLOEvent->m_nPostMouseEventButton,
2340 pLOEvent->m_nPostMouseEventModifier);
2343 static void
2344 openDocumentInThread (gpointer data)
2346 GTask* task = G_TASK(data);
2347 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2348 LOKDocViewPrivate& priv = getPrivate(pDocView);
2350 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2351 if ( priv->m_pDocument )
2353 priv->m_pDocument->pClass->destroy( priv->m_pDocument );
2354 priv->m_pDocument = nullptr;
2357 priv->m_pOffice->pClass->registerCallback(priv->m_pOffice, globalCallbackWorker, pDocView);
2358 std::string url = priv->m_aDocPath;
2359 if (gchar* pURL = g_filename_to_uri(url.c_str(), nullptr, nullptr))
2361 url = pURL;
2362 g_free(pURL);
2364 priv->m_pDocument = priv->m_pOffice->pClass->documentLoadWithOptions( priv->m_pOffice, url.c_str(), "en-US" );
2365 if ( !priv->m_pDocument )
2367 char *pError = priv->m_pOffice->pClass->getError( priv->m_pOffice );
2368 g_task_return_new_error(task, g_quark_from_static_string ("LOK error"), 0, "%s", pError);
2370 else
2372 priv->m_eDocumentType = static_cast<LibreOfficeKitDocumentType>(priv->m_pDocument->pClass->getDocumentType(priv->m_pDocument));
2373 gdk_threads_add_idle(postDocumentLoad, pDocView);
2374 g_task_return_boolean (task, true);
2378 static void
2379 setPartInThread(gpointer data)
2381 GTask* task = G_TASK(data);
2382 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2383 LOKDocViewPrivate& priv = getPrivate(pDocView);
2384 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2385 int nPart = pLOEvent->m_nPart;
2387 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2388 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2389 priv->m_pDocument->pClass->setPart( priv->m_pDocument, nPart );
2390 aGuard.unlock();
2392 lok_doc_view_reset_view(pDocView);
2395 static void
2396 setPartmodeInThread(gpointer data)
2398 GTask* task = G_TASK(data);
2399 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2400 LOKDocViewPrivate& priv = getPrivate(pDocView);
2401 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2402 int nPartMode = pLOEvent->m_nPartMode;
2404 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2405 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2406 priv->m_pDocument->pClass->setPartMode( priv->m_pDocument, nPartMode );
2409 static void
2410 setEditInThread(gpointer data)
2412 GTask* task = G_TASK(data);
2413 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2414 LOKDocViewPrivate& priv = getPrivate(pDocView);
2415 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2416 bool bWasEdit = priv->m_bEdit;
2417 bool bEdit = pLOEvent->m_bEdit;
2419 if (!priv->m_bEdit && bEdit)
2420 g_info("lok_doc_view_set_edit: entering edit mode");
2421 else if (priv->m_bEdit && !bEdit)
2423 g_info("lok_doc_view_set_edit: leaving edit mode");
2424 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2425 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2426 priv->m_pDocument->pClass->resetSelection(priv->m_pDocument);
2428 priv->m_bEdit = bEdit;
2429 g_signal_emit(pDocView, doc_view_signals[EDIT_CHANGED], 0, bWasEdit);
2430 gdk_threads_add_idle(queueDraw, GTK_WIDGET(pDocView));
2433 static void
2434 postCommandInThread (gpointer data)
2436 GTask* task = G_TASK(data);
2437 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2438 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2439 LOKDocViewPrivate& priv = getPrivate(pDocView);
2441 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
2442 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2443 std::stringstream ss;
2444 ss << "lok::Document::postUnoCommand(" << pLOEvent->m_pCommand << ", " << pLOEvent->m_pArguments << ")";
2445 g_info("%s", ss.str().c_str());
2446 priv->m_pDocument->pClass->postUnoCommand(priv->m_pDocument, pLOEvent->m_pCommand, pLOEvent->m_pArguments, pLOEvent->m_bNotifyWhenFinished);
2449 static void
2450 paintTileInThread (gpointer data)
2452 GTask* task = G_TASK(data);
2453 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2454 LOKDocViewPrivate& priv = getPrivate(pDocView);
2455 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2456 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
2457 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
2459 // check if "source" tile buffer is different from "current" tile buffer
2460 if (pLOEvent->m_pTileBuffer != &*priv->m_pTileBuffer)
2462 pLOEvent->m_pTileBuffer = nullptr;
2463 g_task_return_new_error(task,
2464 LOK_TILEBUFFER_ERROR,
2465 LOK_TILEBUFFER_CHANGED,
2466 "TileBuffer has changed");
2467 return;
2469 std::unique_ptr<TileBuffer>& buffer = priv->m_pTileBuffer;
2470 if (buffer->hasValidTile(pLOEvent->m_nPaintTileX, pLOEvent->m_nPaintTileY))
2471 return;
2473 cairo_surface_t *pSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nTileSizePixelsScaled, nTileSizePixelsScaled);
2474 if (cairo_surface_status(pSurface) != CAIRO_STATUS_SUCCESS)
2476 cairo_surface_destroy(pSurface);
2477 g_task_return_new_error(task,
2478 LOK_TILEBUFFER_ERROR,
2479 LOK_TILEBUFFER_MEMORY,
2480 "Error allocating Surface");
2481 return;
2484 unsigned char* pBuffer = cairo_image_surface_get_data(pSurface);
2485 GdkRectangle aTileRectangle;
2486 aTileRectangle.x = pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor) * pLOEvent->m_nPaintTileY;
2487 aTileRectangle.y = pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor) * pLOEvent->m_nPaintTileX;
2489 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2490 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2491 std::stringstream ss;
2492 GTimer* aTimer = g_timer_new();
2493 gulong nElapsedMs;
2494 ss << "lok::Document::paintTile(" << static_cast<void*>(pBuffer) << ", "
2495 << nTileSizePixelsScaled << ", " << nTileSizePixelsScaled << ", "
2496 << aTileRectangle.x << ", " << aTileRectangle.y << ", "
2497 << pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor) << ", "
2498 << pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor) << ")";
2500 priv->m_pDocument->pClass->paintTile(priv->m_pDocument,
2501 pBuffer,
2502 nTileSizePixelsScaled, nTileSizePixelsScaled,
2503 aTileRectangle.x, aTileRectangle.y,
2504 pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor),
2505 pixelToTwip(nTileSizePixelsScaled, pLOEvent->m_fPaintTileZoom * nScaleFactor));
2506 aGuard.unlock();
2508 g_timer_elapsed(aTimer, &nElapsedMs);
2509 ss << " rendered in " << (nElapsedMs / 1000.) << " milliseconds";
2510 g_info("%s", ss.str().c_str());
2511 g_timer_destroy(aTimer);
2513 cairo_surface_mark_dirty(pSurface);
2515 // Its likely that while the tilebuffer has changed, one of the paint tile
2516 // requests has passed the previous check at start of this function, and has
2517 // rendered the tile already. We want to stop such rendered tiles from being
2518 // stored in new tile buffer.
2519 if (pLOEvent->m_pTileBuffer != &*priv->m_pTileBuffer)
2521 pLOEvent->m_pTileBuffer = nullptr;
2522 g_task_return_new_error(task,
2523 LOK_TILEBUFFER_ERROR,
2524 LOK_TILEBUFFER_CHANGED,
2525 "TileBuffer has changed");
2526 return;
2529 g_task_return_pointer(task, pSurface, reinterpret_cast<GDestroyNotify>(cairo_surface_destroy));
2533 static void
2534 lokThreadFunc(gpointer data, gpointer /*user_data*/)
2536 GTask* task = G_TASK(data);
2537 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2538 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2539 LOKDocViewPrivate& priv = getPrivate(pDocView);
2541 switch (pLOEvent->m_nType)
2543 case LOK_LOAD_DOC:
2544 openDocumentInThread(task);
2545 break;
2546 case LOK_POST_COMMAND:
2547 postCommandInThread(task);
2548 break;
2549 case LOK_SET_EDIT:
2550 setEditInThread(task);
2551 break;
2552 case LOK_SET_PART:
2553 setPartInThread(task);
2554 break;
2555 case LOK_SET_PARTMODE:
2556 setPartmodeInThread(task);
2557 break;
2558 case LOK_POST_KEY:
2559 // view-only/editable mode already checked during signal key signal emission
2560 postKeyEventInThread(task);
2561 break;
2562 case LOK_PAINT_TILE:
2563 paintTileInThread(task);
2564 break;
2565 case LOK_POST_MOUSE_EVENT:
2566 postMouseEventInThread(task);
2567 break;
2568 case LOK_SET_GRAPHIC_SELECTION:
2569 if (priv->m_bEdit)
2570 setGraphicSelectionInThread(task);
2571 else
2572 g_info ("LOK_SET_GRAPHIC_SELECTION: skipping graphical operation in view-only mode");
2573 break;
2574 case LOK_SET_CLIENT_ZOOM:
2575 setClientZoomInThread(task);
2576 break;
2579 g_object_unref(task);
2582 static void
2583 onStyleContextChanged (LOKDocView* pDocView)
2585 // The scale factor might have changed
2586 updateClientZoom (pDocView);
2587 gtk_widget_queue_draw (GTK_WIDGET (pDocView));
2590 static void lok_doc_view_init (LOKDocView* pDocView)
2592 LOKDocViewPrivate& priv = getPrivate(pDocView);
2593 priv.m_pImpl = new LOKDocViewPrivateImpl();
2595 gtk_widget_add_events(GTK_WIDGET(pDocView),
2596 GDK_BUTTON_PRESS_MASK
2597 |GDK_BUTTON_RELEASE_MASK
2598 |GDK_BUTTON_MOTION_MASK
2599 |GDK_KEY_PRESS_MASK
2600 |GDK_KEY_RELEASE_MASK);
2602 priv->lokThreadPool = g_thread_pool_new(lokThreadFunc,
2603 nullptr,
2605 FALSE,
2606 nullptr);
2608 g_signal_connect (pDocView, "style-updated", G_CALLBACK(onStyleContextChanged), nullptr);
2611 static void lok_doc_view_set_property (GObject* object, guint propId, const GValue *value, GParamSpec *pspec)
2613 LOKDocView* pDocView = LOK_DOC_VIEW (object);
2614 LOKDocViewPrivate& priv = getPrivate(pDocView);
2615 bool bDocPasswordEnabled = priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD;
2616 bool bDocPasswordToModifyEnabled = priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY;
2617 bool bTiledAnnotationsEnabled = !(priv->m_nLOKFeatures & LOK_FEATURE_NO_TILED_ANNOTATIONS);
2619 switch (propId)
2621 case PROP_LO_PATH:
2622 priv->m_aLOPath = g_value_get_string (value);
2623 break;
2624 case PROP_LO_UNIPOLL:
2625 priv->m_bUnipoll = g_value_get_boolean (value);
2626 break;
2627 case PROP_LO_POINTER:
2628 priv->m_pOffice = static_cast<LibreOfficeKit*>(g_value_get_pointer(value));
2629 break;
2630 case PROP_USER_PROFILE_URL:
2631 if (const gchar* pUserProfile = g_value_get_string(value))
2632 priv->m_aUserProfileURL = pUserProfile;
2633 break;
2634 case PROP_DOC_PATH:
2635 priv->m_aDocPath = g_value_get_string (value);
2636 break;
2637 case PROP_DOC_POINTER:
2638 priv->m_pDocument = static_cast<LibreOfficeKitDocument*>(g_value_get_pointer(value));
2639 priv->m_eDocumentType = static_cast<LibreOfficeKitDocumentType>(priv->m_pDocument->pClass->getDocumentType(priv->m_pDocument));
2640 break;
2641 case PROP_EDITABLE:
2642 lok_doc_view_set_edit (pDocView, g_value_get_boolean (value));
2643 break;
2644 case PROP_ZOOM:
2645 lok_doc_view_set_zoom (pDocView, g_value_get_float (value));
2646 break;
2647 case PROP_DOC_WIDTH:
2648 priv->m_nDocumentWidthTwips = g_value_get_long (value);
2649 break;
2650 case PROP_DOC_HEIGHT:
2651 priv->m_nDocumentHeightTwips = g_value_get_long (value);
2652 break;
2653 case PROP_DOC_PASSWORD:
2654 if (bool(g_value_get_boolean (value)) != bDocPasswordEnabled)
2656 priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_DOCUMENT_PASSWORD;
2657 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2659 break;
2660 case PROP_DOC_PASSWORD_TO_MODIFY:
2661 if ( bool(g_value_get_boolean (value)) != bDocPasswordToModifyEnabled)
2663 priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY;
2664 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2666 break;
2667 case PROP_TILED_ANNOTATIONS:
2668 if ( bool(g_value_get_boolean (value)) != bTiledAnnotationsEnabled)
2670 priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_NO_TILED_ANNOTATIONS;
2671 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2673 break;
2674 default:
2675 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec);
2679 static void lok_doc_view_get_property (GObject* object, guint propId, GValue *value, GParamSpec *pspec)
2681 LOKDocView* pDocView = LOK_DOC_VIEW (object);
2682 LOKDocViewPrivate& priv = getPrivate(pDocView);
2684 switch (propId)
2686 case PROP_LO_PATH:
2687 g_value_set_string (value, priv->m_aLOPath.c_str());
2688 break;
2689 case PROP_LO_UNIPOLL:
2690 g_value_set_boolean (value, priv->m_bUnipoll);
2691 break;
2692 case PROP_LO_POINTER:
2693 g_value_set_pointer(value, priv->m_pOffice);
2694 break;
2695 case PROP_USER_PROFILE_URL:
2696 g_value_set_string(value, priv->m_aUserProfileURL.c_str());
2697 break;
2698 case PROP_DOC_PATH:
2699 g_value_set_string (value, priv->m_aDocPath.c_str());
2700 break;
2701 case PROP_DOC_POINTER:
2702 g_value_set_pointer(value, priv->m_pDocument);
2703 break;
2704 case PROP_EDITABLE:
2705 g_value_set_boolean (value, priv->m_bEdit);
2706 break;
2707 case PROP_LOAD_PROGRESS:
2708 g_value_set_double (value, priv->m_nLoadProgress);
2709 break;
2710 case PROP_ZOOM:
2711 g_value_set_float (value, priv->m_fZoom);
2712 break;
2713 case PROP_IS_LOADING:
2714 g_value_set_boolean (value, priv->m_bIsLoading);
2715 break;
2716 case PROP_IS_INITIALIZED:
2717 g_value_set_boolean (value, priv->m_bInit);
2718 break;
2719 case PROP_DOC_WIDTH:
2720 g_value_set_long (value, priv->m_nDocumentWidthTwips);
2721 break;
2722 case PROP_DOC_HEIGHT:
2723 g_value_set_long (value, priv->m_nDocumentHeightTwips);
2724 break;
2725 case PROP_CAN_ZOOM_IN:
2726 g_value_set_boolean (value, priv->m_bCanZoomIn);
2727 break;
2728 case PROP_CAN_ZOOM_OUT:
2729 g_value_set_boolean (value, priv->m_bCanZoomOut);
2730 break;
2731 case PROP_DOC_PASSWORD:
2732 g_value_set_boolean (value, (priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD) != 0);
2733 break;
2734 case PROP_DOC_PASSWORD_TO_MODIFY:
2735 g_value_set_boolean (value, (priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY) != 0);
2736 break;
2737 case PROP_TILED_ANNOTATIONS:
2738 g_value_set_boolean (value, !(priv->m_nLOKFeatures & LOK_FEATURE_NO_TILED_ANNOTATIONS));
2739 break;
2740 default:
2741 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec);
2745 static gboolean lok_doc_view_draw (GtkWidget* pWidget, cairo_t* pCairo)
2747 LOKDocView *pDocView = LOK_DOC_VIEW (pWidget);
2749 renderDocument (pDocView, pCairo);
2750 renderOverlay (pDocView, pCairo);
2752 return FALSE;
2755 //rhbz#1444437 finalize may not occur immediately when this widget is destroyed
2756 //it may happen during GC of javascript, e.g. in gnome-documents but "destroy"
2757 //will be called promptly, so close documents in destroy, not finalize
2758 static void lok_doc_view_destroy (GtkWidget* widget)
2760 LOKDocView* pDocView = LOK_DOC_VIEW (widget);
2761 LOKDocViewPrivate& priv = getPrivate(pDocView);
2763 // Ignore notifications sent to this view on shutdown.
2764 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2765 if (priv->m_pDocument)
2767 setDocumentView(priv->m_pDocument, priv->m_nViewId);
2768 priv->m_pDocument->pClass->registerCallback(priv->m_pDocument, nullptr, nullptr);
2771 if (priv->lokThreadPool)
2773 g_thread_pool_free(priv->lokThreadPool, true, true);
2774 priv->lokThreadPool = nullptr;
2777 aGuard.unlock();
2779 if (priv->m_pDocument)
2781 // This call may drop several views - e.g., embedded OLE in-place clients
2782 priv->m_pDocument->pClass->destroyView(priv->m_pDocument, priv->m_nViewId);
2783 if (priv->m_pDocument->pClass->getViewsCount(priv->m_pDocument) == 0)
2785 // Last view(s) gone
2786 priv->m_pDocument->pClass->destroy (priv->m_pDocument);
2787 priv->m_pDocument = nullptr;
2788 if (priv->m_pOffice)
2790 priv->m_pOffice->pClass->destroy (priv->m_pOffice);
2791 priv->m_pOffice = nullptr;
2796 GTK_WIDGET_CLASS (lok_doc_view_parent_class)->destroy (widget);
2799 static void lok_doc_view_finalize (GObject* object)
2801 LOKDocView* pDocView = LOK_DOC_VIEW (object);
2802 LOKDocViewPrivate& priv = getPrivate(pDocView);
2804 delete priv.m_pImpl;
2805 priv.m_pImpl = nullptr;
2807 G_OBJECT_CLASS (lok_doc_view_parent_class)->finalize (object);
2810 // kicks the mainloop awake
2811 static gboolean timeout_wakeup(void *)
2813 return FALSE;
2816 // integrate our mainloop with LOK's
2817 static int lok_poll_callback(void*, int timeoutUs)
2819 bool bWasEvent(false);
2820 if (timeoutUs > 0)
2822 guint timeout = g_timeout_add(timeoutUs / 1000, timeout_wakeup, nullptr);
2823 bWasEvent = g_main_context_iteration(nullptr, true);
2824 g_source_remove(timeout);
2826 else
2827 bWasEvent = g_main_context_iteration(nullptr, timeoutUs < 0);
2829 return bWasEvent ? 1 : 0;
2832 // thread-safe wakeup of our mainloop
2833 static void lok_wake_callback(void *)
2835 g_main_context_wakeup(nullptr);
2838 static gboolean spin_lok_loop(void *pData)
2840 LOKDocView *pDocView = LOK_DOC_VIEW (pData);
2841 LOKDocViewPrivate& priv = getPrivate(pDocView);
2842 priv->m_pOffice->pClass->runLoop(priv->m_pOffice, lok_poll_callback, lok_wake_callback, nullptr);
2843 return FALSE;
2846 // Update the client's view size
2847 static void updateClientZoom(LOKDocView *pDocView)
2849 LOKDocViewPrivate& priv = getPrivate(pDocView);
2850 if (!priv->m_fZoom)
2851 return; // Not initialized yet?
2852 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
2853 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
2854 GError* error = nullptr;
2856 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2857 LOEvent* pLOEvent = new LOEvent(LOK_SET_CLIENT_ZOOM);
2858 pLOEvent->m_nTilePixelWidth = nTileSizePixelsScaled;
2859 pLOEvent->m_nTilePixelHeight = nTileSizePixelsScaled;
2860 pLOEvent->m_nTileTwipWidth = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom * nScaleFactor);
2861 pLOEvent->m_nTileTwipHeight = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom * nScaleFactor);
2862 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2864 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2865 if (error != nullptr)
2867 g_warning("Unable to call LOK_SET_CLIENT_ZOOM: %s", error->message);
2868 g_clear_error(&error);
2870 g_object_unref(task);
2872 priv->m_nTileSizeTwips = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom * nScaleFactor);
2875 static gboolean lok_doc_view_initable_init (GInitable *initable, GCancellable* /*cancellable*/, GError **error)
2877 LOKDocView *pDocView = LOK_DOC_VIEW (initable);
2878 LOKDocViewPrivate& priv = getPrivate(pDocView);
2880 if (priv->m_pOffice != nullptr)
2881 return true;
2883 if (priv->m_bUnipoll)
2884 (void)g_setenv("SAL_LOK_OPTIONS", "unipoll", FALSE);
2886 static const char testingLangs[] = "de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru";
2887 (void)g_setenv("LOK_ALLOWLIST_LANGUAGES", testingLangs, FALSE);
2889 priv->m_pOffice = lok_init_2(priv->m_aLOPath.c_str(), priv->m_aUserProfileURL.empty() ? nullptr : priv->m_aUserProfileURL.c_str());
2891 if (priv->m_pOffice == nullptr)
2893 g_set_error (error,
2894 g_quark_from_static_string ("LOK initialization error"), 0,
2895 "Failed to get LibreOfficeKit context. Make sure path (%s) is correct",
2896 priv->m_aLOPath.c_str());
2897 return FALSE;
2899 priv->m_nLOKFeatures |= LOK_FEATURE_PART_IN_INVALIDATION_CALLBACK;
2900 priv->m_nLOKFeatures |= LOK_FEATURE_VIEWID_IN_VISCURSOR_INVALIDATION_CALLBACK;
2901 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2903 if (priv->m_bUnipoll)
2904 g_idle_add(spin_lok_loop, pDocView);
2906 return true;
2909 static void lok_doc_view_initable_iface_init (GInitableIface *iface)
2911 iface->init = lok_doc_view_initable_init;
2914 static void lok_doc_view_class_init (LOKDocViewClass* pClass)
2916 GObjectClass *pGObjectClass = G_OBJECT_CLASS(pClass);
2917 GtkWidgetClass *pWidgetClass = GTK_WIDGET_CLASS(pClass);
2919 pGObjectClass->get_property = lok_doc_view_get_property;
2920 pGObjectClass->set_property = lok_doc_view_set_property;
2921 pGObjectClass->finalize = lok_doc_view_finalize;
2923 pWidgetClass->draw = lok_doc_view_draw;
2924 pWidgetClass->button_press_event = lok_doc_view_signal_button;
2925 pWidgetClass->button_release_event = lok_doc_view_signal_button;
2926 pWidgetClass->key_press_event = signalKey;
2927 pWidgetClass->key_release_event = signalKey;
2928 pWidgetClass->motion_notify_event = lok_doc_view_signal_motion;
2929 pWidgetClass->destroy = lok_doc_view_destroy;
2932 * LOKDocView:lopath:
2934 * The absolute path of the LibreOffice install.
2936 properties[PROP_LO_PATH] =
2937 g_param_spec_string("lopath",
2938 "LO Path",
2939 "LibreOffice Install Path",
2940 nullptr,
2941 static_cast<GParamFlags>(G_PARAM_READWRITE |
2942 G_PARAM_CONSTRUCT_ONLY |
2943 G_PARAM_STATIC_STRINGS));
2946 * LOKDocView:unipoll:
2948 * Whether we use our own unified polling mainloop in place of glib's
2950 properties[PROP_LO_UNIPOLL] =
2951 g_param_spec_boolean("unipoll",
2952 "Unified Polling",
2953 "Whether we use a custom unified polling loop",
2954 FALSE,
2955 static_cast<GParamFlags>(G_PARAM_READWRITE |
2956 G_PARAM_CONSTRUCT_ONLY |
2957 G_PARAM_STATIC_STRINGS));
2959 * LOKDocView:lopointer:
2961 * A LibreOfficeKit* in case lok_init() is already called
2962 * previously.
2964 properties[PROP_LO_POINTER] =
2965 g_param_spec_pointer("lopointer",
2966 "LO Pointer",
2967 "A LibreOfficeKit* from lok_init()",
2968 static_cast<GParamFlags>(G_PARAM_READWRITE |
2969 G_PARAM_CONSTRUCT_ONLY |
2970 G_PARAM_STATIC_STRINGS));
2973 * LOKDocView:userprofileurl:
2975 * The absolute path of the LibreOffice user profile.
2977 properties[PROP_USER_PROFILE_URL] =
2978 g_param_spec_string("userprofileurl",
2979 "User profile path",
2980 "LibreOffice user profile path",
2981 nullptr,
2982 static_cast<GParamFlags>(G_PARAM_READWRITE |
2983 G_PARAM_CONSTRUCT_ONLY |
2984 G_PARAM_STATIC_STRINGS));
2987 * LOKDocView:docpath:
2989 * The path of the document that is currently being viewed.
2991 properties[PROP_DOC_PATH] =
2992 g_param_spec_string("docpath",
2993 "Document Path",
2994 "The URI of the document to open",
2995 nullptr,
2996 static_cast<GParamFlags>(G_PARAM_READWRITE |
2997 G_PARAM_STATIC_STRINGS));
3000 * LOKDocView:docpointer:
3002 * A LibreOfficeKitDocument* in case documentLoad() is already called
3003 * previously.
3005 properties[PROP_DOC_POINTER] =
3006 g_param_spec_pointer("docpointer",
3007 "Document Pointer",
3008 "A LibreOfficeKitDocument* from documentLoad()",
3009 static_cast<GParamFlags>(G_PARAM_READWRITE |
3010 G_PARAM_STATIC_STRINGS));
3013 * LOKDocView:editable:
3015 * Whether the document loaded inside of #LOKDocView is editable or not.
3017 properties[PROP_EDITABLE] =
3018 g_param_spec_boolean("editable",
3019 "Editable",
3020 "Whether the content is in edit mode or not",
3021 FALSE,
3022 static_cast<GParamFlags>(G_PARAM_READWRITE |
3023 G_PARAM_STATIC_STRINGS));
3026 * LOKDocView:load-progress:
3028 * The percent completion of the current loading operation of the
3029 * document. This can be used for progress bars. Note that this is not a
3030 * very accurate progress indicator, and its value might reset it couple of
3031 * times to 0 and start again. You should not rely on its numbers.
3033 properties[PROP_LOAD_PROGRESS] =
3034 g_param_spec_double("load-progress",
3035 "Estimated Load Progress",
3036 "Shows the progress of the document load operation",
3037 0.0, 1.0, 0.0,
3038 static_cast<GParamFlags>(G_PARAM_READABLE |
3039 G_PARAM_STATIC_STRINGS));
3042 * LOKDocView:zoom-level:
3044 * The current zoom level of the document loaded inside #LOKDocView. The
3045 * default value is 1.0.
3047 properties[PROP_ZOOM] =
3048 g_param_spec_float("zoom-level",
3049 "Zoom Level",
3050 "The current zoom level of the content",
3051 0, 5.0, 1.0,
3052 static_cast<GParamFlags>(G_PARAM_READWRITE |
3053 G_PARAM_STATIC_STRINGS));
3056 * LOKDocView:is-loading:
3058 * Whether the requested document is being loaded or not. %TRUE if it is
3059 * being loaded, otherwise %FALSE.
3061 properties[PROP_IS_LOADING] =
3062 g_param_spec_boolean("is-loading",
3063 "Is Loading",
3064 "Whether the view is loading a document",
3065 FALSE,
3066 static_cast<GParamFlags>(G_PARAM_READABLE |
3067 G_PARAM_STATIC_STRINGS));
3070 * LOKDocView:is-initialized:
3072 * Whether the requested document has completely loaded or not.
3074 properties[PROP_IS_INITIALIZED] =
3075 g_param_spec_boolean("is-initialized",
3076 "Has initialized",
3077 "Whether the view has completely initialized",
3078 FALSE,
3079 static_cast<GParamFlags>(G_PARAM_READABLE |
3080 G_PARAM_STATIC_STRINGS));
3083 * LOKDocView:doc-width:
3085 * The width of the currently loaded document in #LOKDocView in twips.
3087 properties[PROP_DOC_WIDTH] =
3088 g_param_spec_long("doc-width",
3089 "Document Width",
3090 "Width of the document in twips",
3091 0, G_MAXLONG, 0,
3092 static_cast<GParamFlags>(G_PARAM_READWRITE |
3093 G_PARAM_STATIC_STRINGS));
3096 * LOKDocView:doc-height:
3098 * The height of the currently loaded document in #LOKDocView in twips.
3100 properties[PROP_DOC_HEIGHT] =
3101 g_param_spec_long("doc-height",
3102 "Document Height",
3103 "Height of the document in twips",
3104 0, G_MAXLONG, 0,
3105 static_cast<GParamFlags>(G_PARAM_READWRITE |
3106 G_PARAM_STATIC_STRINGS));
3109 * LOKDocView:can-zoom-in:
3111 * It tells whether the view can further be zoomed in or not.
3113 properties[PROP_CAN_ZOOM_IN] =
3114 g_param_spec_boolean("can-zoom-in",
3115 "Can Zoom In",
3116 "Whether the view can be zoomed in further",
3117 true,
3118 static_cast<GParamFlags>(G_PARAM_READABLE
3119 | G_PARAM_STATIC_STRINGS));
3122 * LOKDocView:can-zoom-out:
3124 * It tells whether the view can further be zoomed out or not.
3126 properties[PROP_CAN_ZOOM_OUT] =
3127 g_param_spec_boolean("can-zoom-out",
3128 "Can Zoom Out",
3129 "Whether the view can be zoomed out further",
3130 true,
3131 static_cast<GParamFlags>(G_PARAM_READABLE
3132 | G_PARAM_STATIC_STRINGS));
3135 * LOKDocView:doc-password:
3137 * Set it to true if client supports providing password for viewing
3138 * password protected documents
3140 properties[PROP_DOC_PASSWORD] =
3141 g_param_spec_boolean("doc-password",
3142 "Document password capability",
3143 "Whether client supports providing document passwords",
3144 FALSE,
3145 static_cast<GParamFlags>(G_PARAM_READWRITE
3146 | G_PARAM_STATIC_STRINGS));
3149 * LOKDocView:doc-password-to-modify:
3151 * Set it to true if client supports providing password for edit-protected documents
3153 properties[PROP_DOC_PASSWORD_TO_MODIFY] =
3154 g_param_spec_boolean("doc-password-to-modify",
3155 "Edit document password capability",
3156 "Whether the client supports providing passwords to edit documents",
3157 FALSE,
3158 static_cast<GParamFlags>(G_PARAM_READWRITE
3159 | G_PARAM_STATIC_STRINGS));
3162 * LOKDocView:tiled-annotations-rendering:
3164 * Set it to false if client does not want LO to render comments in tiles and
3165 * instead interested in using comments API to access comments
3167 properties[PROP_TILED_ANNOTATIONS] =
3168 g_param_spec_boolean("tiled-annotations",
3169 "Render comments in tiles",
3170 "Whether the client wants in tile comment rendering",
3171 true,
3172 static_cast<GParamFlags>(G_PARAM_READWRITE
3173 | G_PARAM_STATIC_STRINGS));
3175 g_object_class_install_properties(pGObjectClass, PROP_LAST, properties);
3178 * LOKDocView::load-changed:
3179 * @pDocView: the #LOKDocView on which the signal is emitted
3180 * @fLoadProgress: the new progress value
3182 doc_view_signals[LOAD_CHANGED] =
3183 g_signal_new("load-changed",
3184 G_TYPE_FROM_CLASS (pGObjectClass),
3185 G_SIGNAL_RUN_FIRST,
3187 nullptr, nullptr,
3188 g_cclosure_marshal_VOID__DOUBLE,
3189 G_TYPE_NONE, 1,
3190 G_TYPE_DOUBLE);
3193 * LOKDocView::edit-changed:
3194 * @pDocView: the #LOKDocView on which the signal is emitted
3195 * @bEdit: the new edit value of the view
3197 doc_view_signals[EDIT_CHANGED] =
3198 g_signal_new("edit-changed",
3199 G_TYPE_FROM_CLASS (pGObjectClass),
3200 G_SIGNAL_RUN_FIRST,
3202 nullptr, nullptr,
3203 g_cclosure_marshal_VOID__BOOLEAN,
3204 G_TYPE_NONE, 1,
3205 G_TYPE_BOOLEAN);
3208 * LOKDocView::command-changed:
3209 * @pDocView: the #LOKDocView on which the signal is emitted
3210 * @aCommand: the command that was changed
3212 doc_view_signals[COMMAND_CHANGED] =
3213 g_signal_new("command-changed",
3214 G_TYPE_FROM_CLASS(pGObjectClass),
3215 G_SIGNAL_RUN_FIRST,
3217 nullptr, nullptr,
3218 g_cclosure_marshal_VOID__STRING,
3219 G_TYPE_NONE, 1,
3220 G_TYPE_STRING);
3223 * LOKDocView::search-not-found:
3224 * @pDocView: the #LOKDocView on which the signal is emitted
3225 * @aCommand: the string for which the search was not found.
3227 doc_view_signals[SEARCH_NOT_FOUND] =
3228 g_signal_new("search-not-found",
3229 G_TYPE_FROM_CLASS(pGObjectClass),
3230 G_SIGNAL_RUN_FIRST,
3232 nullptr, nullptr,
3233 g_cclosure_marshal_VOID__STRING,
3234 G_TYPE_NONE, 1,
3235 G_TYPE_STRING);
3238 * LOKDocView::part-changed:
3239 * @pDocView: the #LOKDocView on which the signal is emitted
3240 * @aCommand: the part number which the view changed to
3242 doc_view_signals[PART_CHANGED] =
3243 g_signal_new("part-changed",
3244 G_TYPE_FROM_CLASS(pGObjectClass),
3245 G_SIGNAL_RUN_FIRST,
3247 nullptr, nullptr,
3248 g_cclosure_marshal_VOID__INT,
3249 G_TYPE_NONE, 1,
3250 G_TYPE_INT);
3253 * LOKDocView::size-changed:
3254 * @pDocView: the #LOKDocView on which the signal is emitted
3255 * @aCommand: NULL, we just notify that want to notify the UI elements that are interested.
3257 doc_view_signals[SIZE_CHANGED] =
3258 g_signal_new("size-changed",
3259 G_TYPE_FROM_CLASS(pGObjectClass),
3260 G_SIGNAL_RUN_FIRST,
3262 nullptr, nullptr,
3263 g_cclosure_marshal_VOID__VOID,
3264 G_TYPE_NONE, 1,
3265 G_TYPE_INT);
3268 * LOKDocView::hyperlinked-clicked:
3269 * @pDocView: the #LOKDocView on which the signal is emitted
3270 * @aHyperlink: the URI which the application should handle
3272 doc_view_signals[HYPERLINK_CLICKED] =
3273 g_signal_new("hyperlink-clicked",
3274 G_TYPE_FROM_CLASS(pGObjectClass),
3275 G_SIGNAL_RUN_FIRST,
3277 nullptr, nullptr,
3278 g_cclosure_marshal_VOID__STRING,
3279 G_TYPE_NONE, 1,
3280 G_TYPE_STRING);
3283 * LOKDocView::cursor-changed:
3284 * @pDocView: the #LOKDocView on which the signal is emitted
3285 * @nX: The new cursor position (X coordinate) in pixels
3286 * @nY: The new cursor position (Y coordinate) in pixels
3287 * @nWidth: The width of new cursor
3288 * @nHeight: The height of new cursor
3290 doc_view_signals[CURSOR_CHANGED] =
3291 g_signal_new("cursor-changed",
3292 G_TYPE_FROM_CLASS(pGObjectClass),
3293 G_SIGNAL_RUN_FIRST,
3295 nullptr, nullptr,
3296 g_cclosure_marshal_generic,
3297 G_TYPE_NONE, 4,
3298 G_TYPE_INT, G_TYPE_INT,
3299 G_TYPE_INT, G_TYPE_INT);
3302 * LOKDocView::search-result-count:
3303 * @pDocView: the #LOKDocView on which the signal is emitted
3304 * @aCommand: number of matches.
3306 doc_view_signals[SEARCH_RESULT_COUNT] =
3307 g_signal_new("search-result-count",
3308 G_TYPE_FROM_CLASS(pGObjectClass),
3309 G_SIGNAL_RUN_FIRST,
3311 nullptr, nullptr,
3312 g_cclosure_marshal_VOID__STRING,
3313 G_TYPE_NONE, 1,
3314 G_TYPE_STRING);
3317 * LOKDocView::command-result:
3318 * @pDocView: the #LOKDocView on which the signal is emitted
3319 * @aCommand: JSON containing the info about the command that finished,
3320 * and its success status.
3322 doc_view_signals[COMMAND_RESULT] =
3323 g_signal_new("command-result",
3324 G_TYPE_FROM_CLASS(pGObjectClass),
3325 G_SIGNAL_RUN_FIRST,
3327 nullptr, nullptr,
3328 g_cclosure_marshal_VOID__STRING,
3329 G_TYPE_NONE, 1,
3330 G_TYPE_STRING);
3333 * LOKDocView::address-changed:
3334 * @pDocView: the #LOKDocView on which the signal is emitted
3335 * @aCommand: formula text content
3337 doc_view_signals[ADDRESS_CHANGED] =
3338 g_signal_new("address-changed",
3339 G_TYPE_FROM_CLASS(pGObjectClass),
3340 G_SIGNAL_RUN_FIRST,
3342 nullptr, nullptr,
3343 g_cclosure_marshal_VOID__STRING,
3344 G_TYPE_NONE, 1,
3345 G_TYPE_STRING);
3348 * LOKDocView::formula-changed:
3349 * @pDocView: the #LOKDocView on which the signal is emitted
3350 * @aCommand: formula text content
3352 doc_view_signals[FORMULA_CHANGED] =
3353 g_signal_new("formula-changed",
3354 G_TYPE_FROM_CLASS(pGObjectClass),
3355 G_SIGNAL_RUN_FIRST,
3357 nullptr, nullptr,
3358 g_cclosure_marshal_VOID__STRING,
3359 G_TYPE_NONE, 1,
3360 G_TYPE_STRING);
3363 * LOKDocView::text-selection:
3364 * @pDocView: the #LOKDocView on which the signal is emitted
3365 * @bIsTextSelected: whether text selected is non-null
3367 doc_view_signals[TEXT_SELECTION] =
3368 g_signal_new("text-selection",
3369 G_TYPE_FROM_CLASS(pGObjectClass),
3370 G_SIGNAL_RUN_FIRST,
3372 nullptr, nullptr,
3373 g_cclosure_marshal_VOID__BOOLEAN,
3374 G_TYPE_NONE, 1,
3375 G_TYPE_BOOLEAN);
3378 * LOKDocView::content-control:
3379 * @pDocView: the #LOKDocView on which the signal is emitted
3380 * @pPayload: the JSON string containing the information about ruler properties
3382 doc_view_signals[CONTENT_CONTROL] =
3383 g_signal_new("content-control",
3384 G_TYPE_FROM_CLASS(pGObjectClass),
3385 G_SIGNAL_RUN_FIRST,
3387 nullptr, nullptr,
3388 g_cclosure_marshal_generic,
3389 G_TYPE_NONE, 1,
3390 G_TYPE_STRING);
3393 * LOKDocView::password-required:
3394 * @pDocView: the #LOKDocView on which the signal is emitted
3395 * @pUrl: URL of the document for which password is required
3396 * @bModify: whether password id required to modify the document
3397 * This is true when password is required to edit the document,
3398 * while it can still be viewed without password. In such cases, provide a NULL
3399 * password for read-only access to the document.
3400 * If false, password is required for opening the document, and document
3401 * cannot be opened without providing a valid password.
3403 * Password must be provided by calling lok_doc_view_set_document_password
3404 * function with pUrl as provided by the callback.
3406 * Upon entering an invalid password, another `password-required` signal is
3407 * emitted.
3408 * Upon entering a valid password, document starts to load.
3409 * Upon entering a NULL password: if bModify is %TRUE, document starts to
3410 * open in view-only mode, else loading of document is aborted.
3412 doc_view_signals[PASSWORD_REQUIRED] =
3413 g_signal_new("password-required",
3414 G_TYPE_FROM_CLASS(pGObjectClass),
3415 G_SIGNAL_RUN_FIRST,
3417 nullptr, nullptr,
3418 g_cclosure_marshal_generic,
3419 G_TYPE_NONE, 2,
3420 G_TYPE_STRING,
3421 G_TYPE_BOOLEAN);
3424 * LOKDocView::comment:
3425 * @pDocView: the #LOKDocView on which the signal is emitted
3426 * @pComment: the JSON string containing comment notification
3427 * The has following structure containing the information telling whether
3428 * the comment has been added, deleted or modified.
3429 * The example:
3431 * "comment": {
3432 * "action": "Add",
3433 * "id": "11",
3434 * "parent": "4",
3435 * "author": "Unknown Author",
3436 * "text": "This is a comment",
3437 * "dateTime": "2016-08-18T13:13:00",
3438 * "anchorPos": "4529, 3906",
3439 * "textRange": "1418, 3906, 3111, 919"
3442 * 'action' can be 'Add', 'Remove' or 'Modify' depending on whether
3443 * comment has been added, removed or modified.
3444 * 'parent' is a non-zero comment id if this comment is a reply comment,
3445 * otherwise it's a root comment.
3447 doc_view_signals[COMMENT] =
3448 g_signal_new("comment",
3449 G_TYPE_FROM_CLASS(pGObjectClass),
3450 G_SIGNAL_RUN_FIRST,
3452 nullptr, nullptr,
3453 g_cclosure_marshal_generic,
3454 G_TYPE_NONE, 1,
3455 G_TYPE_STRING);
3458 * LOKDocView::ruler:
3459 * @pDocView: the #LOKDocView on which the signal is emitted
3460 * @pPayload: the JSON string containing the information about ruler properties
3462 * The payload format is:
3465 * "margin1": "...",
3466 * "margin2": "...",
3467 * "leftOffset": "...",
3468 * "pageOffset": "...",
3469 * "pageWidth": "...",
3470 * "unit": "..."
3473 doc_view_signals[RULER] =
3474 g_signal_new("ruler",
3475 G_TYPE_FROM_CLASS(pGObjectClass),
3476 G_SIGNAL_RUN_FIRST,
3478 nullptr, nullptr,
3479 g_cclosure_marshal_generic,
3480 G_TYPE_NONE, 1,
3481 G_TYPE_STRING);
3484 * LOKDocView::window::
3485 * @pDocView: the #LOKDocView on which the signal is emitted
3486 * @pPayload: the JSON string containing the information about the window
3488 * This signal emits information about external windows like dialogs, autopopups for now.
3490 * The payload format of pPayload is:
3493 * "id": "unique integer id of the dialog",
3494 * "action": "<see below>",
3495 * "type": "<see below>"
3496 * "rectangle": "x, y, width, height"
3499 * "type" tells the type of the window the action is associated with
3500 * - "dialog" - window is a dialog
3501 * - "child" - window is a floating window (combo boxes, etc.)
3503 * "action" can take following values:
3504 * - "created" - window is created in the backend, client can render it now
3505 * - "title_changed" - window's title is changed
3506 * - "size_changed" - window's size is changed
3507 * - "invalidate" - the area as described by "rectangle" is invalidated
3508 * Clients must request the new area
3509 * - "cursor_invalidate" - cursor is invalidated. New position is in "rectangle"
3510 * - "cursor_visible" - cursor visible status is changed. Status is available
3511 * in "visible" field
3512 * - "close" - window is closed
3514 doc_view_signals[WINDOW] =
3515 g_signal_new("window",
3516 G_TYPE_FROM_CLASS(pGObjectClass),
3517 G_SIGNAL_RUN_FIRST,
3519 nullptr, nullptr,
3520 g_cclosure_marshal_generic,
3521 G_TYPE_NONE, 1,
3522 G_TYPE_STRING);
3525 * LOKDocView::invalidate-header::
3526 * @pDocView: the #LOKDocView on which the signal is emitted
3527 * @pPayload: can be either "row", "column", or "all".
3529 * The column/row header is no more valid because of a column/row insertion
3530 * or a similar event. Clients must query a new column/row header set.
3532 * The payload says if we are invalidating a row or column header
3534 doc_view_signals[INVALIDATE_HEADER] =
3535 g_signal_new("invalidate-header",
3536 G_TYPE_FROM_CLASS(pGObjectClass),
3537 G_SIGNAL_RUN_FIRST,
3539 nullptr, nullptr,
3540 g_cclosure_marshal_generic,
3541 G_TYPE_NONE, 1,
3542 G_TYPE_STRING);
3545 SAL_DLLPUBLIC_EXPORT GtkWidget*
3546 lok_doc_view_new (const gchar* pPath, GCancellable *cancellable, GError **error)
3548 return GTK_WIDGET (g_initable_new (LOK_TYPE_DOC_VIEW, cancellable, error,
3549 "lopath", pPath == nullptr ? LOK_PATH : pPath,
3550 "halign", GTK_ALIGN_CENTER,
3551 "valign", GTK_ALIGN_CENTER,
3552 nullptr));
3555 SAL_DLLPUBLIC_EXPORT GtkWidget*
3556 lok_doc_view_new_from_user_profile (const gchar* pPath, const gchar* pUserProfile, GCancellable *cancellable, GError **error)
3558 return GTK_WIDGET(g_initable_new(LOK_TYPE_DOC_VIEW, cancellable, error,
3559 "lopath", pPath == nullptr ? LOK_PATH : pPath,
3560 "userprofileurl", pUserProfile,
3561 "halign", GTK_ALIGN_CENTER,
3562 "valign", GTK_ALIGN_CENTER,
3563 nullptr));
3566 SAL_DLLPUBLIC_EXPORT GtkWidget* lok_doc_view_new_from_widget(LOKDocView* pOldLOKDocView,
3567 const gchar* pRenderingArguments)
3569 LOKDocViewPrivate& pOldPriv = getPrivate(pOldLOKDocView);
3570 GtkWidget* pNewDocView = GTK_WIDGET(g_initable_new(LOK_TYPE_DOC_VIEW, /*cancellable=*/nullptr, /*error=*/nullptr,
3571 "lopath", pOldPriv->m_aLOPath.c_str(),
3572 "userprofileurl", pOldPriv->m_aUserProfileURL.c_str(),
3573 "lopointer", pOldPriv->m_pOffice,
3574 "docpointer", pOldPriv->m_pDocument,
3575 "halign", GTK_ALIGN_CENTER,
3576 "valign", GTK_ALIGN_CENTER,
3577 nullptr));
3579 // No documentLoad(), just a createView().
3580 LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(pNewDocView));
3581 LOKDocViewPrivate& pNewPriv = getPrivate(LOK_DOC_VIEW(pNewDocView));
3582 // Store the view id only later in postDocumentLoad(), as
3583 // initializeForRendering() changes the id in Impress.
3584 pDocument->pClass->createView(pDocument);
3585 pNewPriv->m_aRenderingArguments = pRenderingArguments;
3587 postDocumentLoad(pNewDocView);
3588 return pNewDocView;
3591 SAL_DLLPUBLIC_EXPORT gboolean
3592 lok_doc_view_open_document_finish (LOKDocView* pDocView, GAsyncResult* res, GError** error)
3594 GTask* task = G_TASK(res);
3596 g_return_val_if_fail(g_task_is_valid(res, pDocView), false);
3597 g_return_val_if_fail(g_task_get_source_tag(task) == lok_doc_view_open_document, false);
3598 g_return_val_if_fail(error == nullptr || *error == nullptr, false);
3600 return g_task_propagate_boolean(task, error);
3603 SAL_DLLPUBLIC_EXPORT void
3604 lok_doc_view_open_document (LOKDocView* pDocView,
3605 const gchar* pPath,
3606 const gchar* pRenderingArguments,
3607 GCancellable* cancellable,
3608 GAsyncReadyCallback callback,
3609 gpointer userdata)
3611 GTask* task = g_task_new(pDocView, cancellable, callback, userdata);
3612 LOKDocViewPrivate& priv = getPrivate(pDocView);
3613 GError* error = nullptr;
3615 LOEvent* pLOEvent = new LOEvent(LOK_LOAD_DOC);
3617 g_object_set(G_OBJECT(pDocView), "docpath", pPath, nullptr);
3618 if (pRenderingArguments)
3619 priv->m_aRenderingArguments = pRenderingArguments;
3620 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3621 g_task_set_source_tag(task, reinterpret_cast<gpointer>(lok_doc_view_open_document));
3623 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3624 if (error != nullptr)
3626 g_warning("Unable to call LOK_LOAD_DOC: %s", error->message);
3627 g_clear_error(&error);
3629 g_object_unref(task);
3632 SAL_DLLPUBLIC_EXPORT LibreOfficeKitDocument*
3633 lok_doc_view_get_document (LOKDocView* pDocView)
3635 LOKDocViewPrivate& priv = getPrivate(pDocView);
3636 return priv->m_pDocument;
3639 SAL_DLLPUBLIC_EXPORT void
3640 lok_doc_view_set_visible_area (LOKDocView* pDocView, GdkRectangle* pVisibleArea)
3642 if (!pVisibleArea)
3643 return;
3645 LOKDocViewPrivate& priv = getPrivate(pDocView);
3646 priv->m_aVisibleArea = *pVisibleArea;
3647 priv->m_bVisibleAreaSet = true;
3650 namespace {
3651 // This used to be rtl::math::approxEqual() but since that isn't inline anymore
3652 // in rtl/math.hxx and was moved into libuno_sal as rtl_math_approxEqual() to
3653 // cater for representable integer cases and we don't want to link against
3654 // libuno_sal, we'll have to have an own implementation. The special large
3655 // integer cases seems not be needed here.
3656 bool lok_approxEqual(double a, double b)
3658 static const double e48 = 1.0 / (16777216.0 * 16777216.0);
3659 if (a == b)
3660 return true;
3661 if (a == 0.0 || b == 0.0)
3662 return false;
3663 const double d = fabs(a - b);
3664 return (d < fabs(a) * e48 && d < fabs(b) * e48);
3668 SAL_DLLPUBLIC_EXPORT void
3669 lok_doc_view_set_zoom (LOKDocView* pDocView, float fZoom)
3671 LOKDocViewPrivate& priv = getPrivate(pDocView);
3673 if (!priv->m_pDocument)
3674 return;
3676 // Clamp the input value in [MIN_ZOOM, MAX_ZOOM]
3677 fZoom = fZoom < MIN_ZOOM ? MIN_ZOOM : fZoom;
3678 fZoom = std::min(fZoom, MAX_ZOOM);
3680 if (lok_approxEqual(fZoom, priv->m_fZoom))
3681 return;
3683 gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
3684 gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor;
3685 priv->m_fZoom = fZoom;
3686 long nDocumentWidthPixels = twipToPixel(priv->m_nDocumentWidthTwips, fZoom * nScaleFactor);
3687 long nDocumentHeightPixels = twipToPixel(priv->m_nDocumentHeightTwips, fZoom * nScaleFactor);
3688 // Total number of columns in this document.
3689 guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixelsScaled);
3690 priv->m_pTileBuffer = std::make_unique<TileBuffer>(nColumns, nScaleFactor);
3691 gtk_widget_set_size_request(GTK_WIDGET(pDocView),
3692 nDocumentWidthPixels / nScaleFactor,
3693 nDocumentHeightPixels / nScaleFactor);
3695 g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_ZOOM]);
3697 // set properties to indicate if view can be further zoomed in/out
3698 bool bCanZoomIn = priv->m_fZoom < MAX_ZOOM;
3699 bool bCanZoomOut = priv->m_fZoom > MIN_ZOOM;
3700 if (bCanZoomIn != bool(priv->m_bCanZoomIn))
3702 priv->m_bCanZoomIn = bCanZoomIn;
3703 g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_CAN_ZOOM_IN]);
3705 if (bCanZoomOut != bool(priv->m_bCanZoomOut))
3707 priv->m_bCanZoomOut = bCanZoomOut;
3708 g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_CAN_ZOOM_OUT]);
3711 updateClientZoom(pDocView);
3714 SAL_DLLPUBLIC_EXPORT gfloat
3715 lok_doc_view_get_zoom (LOKDocView* pDocView)
3717 LOKDocViewPrivate& priv = getPrivate(pDocView);
3718 return priv->m_fZoom;
3721 SAL_DLLPUBLIC_EXPORT gint
3722 lok_doc_view_get_parts (LOKDocView* pDocView)
3724 LOKDocViewPrivate& priv = getPrivate(pDocView);
3725 if (!priv->m_pDocument)
3726 return -1;
3728 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
3729 setDocumentView(priv->m_pDocument, priv->m_nViewId);
3730 return priv->m_pDocument->pClass->getParts( priv->m_pDocument );
3733 SAL_DLLPUBLIC_EXPORT gint
3734 lok_doc_view_get_part (LOKDocView* pDocView)
3736 LOKDocViewPrivate& priv = getPrivate(pDocView);
3737 if (!priv->m_pDocument)
3738 return -1;
3740 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
3741 setDocumentView(priv->m_pDocument, priv->m_nViewId);
3742 return priv->m_pDocument->pClass->getPart( priv->m_pDocument );
3745 SAL_DLLPUBLIC_EXPORT void
3746 lok_doc_view_set_part (LOKDocView* pDocView, int nPart)
3748 LOKDocViewPrivate& priv = getPrivate(pDocView);
3749 if (!priv->m_pDocument)
3750 return;
3752 if (nPart < 0 || nPart >= priv->m_nParts)
3754 g_warning("Invalid part request : %d", nPart);
3755 return;
3758 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3759 LOEvent* pLOEvent = new LOEvent(LOK_SET_PART);
3760 GError* error = nullptr;
3762 pLOEvent->m_nPart = nPart;
3763 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3765 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3766 if (error != nullptr)
3768 g_warning("Unable to call LOK_SET_PART: %s", error->message);
3769 g_clear_error(&error);
3771 g_object_unref(task);
3772 priv->m_nPartId = nPart;
3775 SAL_DLLPUBLIC_EXPORT void lok_doc_view_send_content_control_event(LOKDocView* pDocView,
3776 const gchar* pArguments)
3778 LOKDocViewPrivate& priv = getPrivate(pDocView);
3779 if (!priv->m_pDocument)
3781 return;
3784 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
3785 setDocumentView(priv->m_pDocument, priv->m_nViewId);
3786 return priv->m_pDocument->pClass->sendContentControlEvent(priv->m_pDocument, pArguments);
3789 SAL_DLLPUBLIC_EXPORT gchar*
3790 lok_doc_view_get_part_name (LOKDocView* pDocView, int nPart)
3792 LOKDocViewPrivate& priv = getPrivate(pDocView);
3793 if (!priv->m_pDocument)
3794 return nullptr;
3796 std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
3797 setDocumentView(priv->m_pDocument, priv->m_nViewId);
3798 return priv->m_pDocument->pClass->getPartName( priv->m_pDocument, nPart );
3801 SAL_DLLPUBLIC_EXPORT void
3802 lok_doc_view_set_partmode(LOKDocView* pDocView,
3803 int nPartMode)
3805 LOKDocViewPrivate& priv = getPrivate(pDocView);
3806 if (!priv->m_pDocument)
3807 return;
3809 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3810 LOEvent* pLOEvent = new LOEvent(LOK_SET_PARTMODE);
3811 GError* error = nullptr;
3813 pLOEvent->m_nPartMode = nPartMode;
3814 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3816 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3817 if (error != nullptr)
3819 g_warning("Unable to call LOK_SET_PARTMODE: %s", error->message);
3820 g_clear_error(&error);
3822 g_object_unref(task);
3825 SAL_DLLPUBLIC_EXPORT void
3826 lok_doc_view_reset_view(LOKDocView* pDocView)
3828 LOKDocViewPrivate& priv = getPrivate(pDocView);
3830 if (priv->m_pTileBuffer != nullptr)
3831 priv->m_pTileBuffer->resetAllTiles();
3832 priv->m_nLoadProgress = 0.0;
3834 memset(&priv->m_aVisibleCursor, 0, sizeof(priv->m_aVisibleCursor));
3835 priv->m_bCursorOverlayVisible = false;
3836 priv->m_bCursorVisible = false;
3838 priv->m_nLastButtonPressTime = 0;
3839 priv->m_nLastButtonReleaseTime = 0;
3840 priv->m_aTextSelectionRectangles.clear();
3841 priv->m_aContentControlRectangles.clear();
3843 memset(&priv->m_aTextSelectionStart, 0, sizeof(priv->m_aTextSelectionStart));
3844 memset(&priv->m_aTextSelectionEnd, 0, sizeof(priv->m_aTextSelectionEnd));
3845 memset(&priv->m_aGraphicSelection, 0, sizeof(priv->m_aGraphicSelection));
3846 priv->m_bInDragGraphicSelection = false;
3847 memset(&priv->m_aCellCursor, 0, sizeof(priv->m_aCellCursor));
3849 cairo_surface_destroy(priv->m_pHandleStart);
3850 priv->m_pHandleStart = nullptr;
3851 memset(&priv->m_aHandleStartRect, 0, sizeof(priv->m_aHandleStartRect));
3852 priv->m_bInDragStartHandle = false;
3854 cairo_surface_destroy(priv->m_pHandleMiddle);
3855 priv->m_pHandleMiddle = nullptr;
3856 memset(&priv->m_aHandleMiddleRect, 0, sizeof(priv->m_aHandleMiddleRect));
3857 priv->m_bInDragMiddleHandle = false;
3859 cairo_surface_destroy(priv->m_pHandleEnd);
3860 priv->m_pHandleEnd = nullptr;
3861 memset(&priv->m_aHandleEndRect, 0, sizeof(priv->m_aHandleEndRect));
3862 priv->m_bInDragEndHandle = false;
3864 memset(&priv->m_aGraphicHandleRects, 0, sizeof(priv->m_aGraphicHandleRects));
3865 memset(&priv->m_bInDragGraphicHandles, 0, sizeof(priv->m_bInDragGraphicHandles));
3867 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
3870 SAL_DLLPUBLIC_EXPORT void
3871 lok_doc_view_set_edit(LOKDocView* pDocView,
3872 gboolean bEdit)
3874 LOKDocViewPrivate& priv = getPrivate(pDocView);
3875 if (!priv->m_pDocument)
3876 return;
3878 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3879 LOEvent* pLOEvent = new LOEvent(LOK_SET_EDIT);
3880 GError* error = nullptr;
3882 pLOEvent->m_bEdit = bEdit;
3883 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3885 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3886 if (error != nullptr)
3888 g_warning("Unable to call LOK_SET_EDIT: %s", error->message);
3889 g_clear_error(&error);
3891 g_object_unref(task);
3894 SAL_DLLPUBLIC_EXPORT gboolean
3895 lok_doc_view_get_edit (LOKDocView* pDocView)
3897 LOKDocViewPrivate& priv = getPrivate(pDocView);
3898 return priv->m_bEdit;
3901 SAL_DLLPUBLIC_EXPORT void
3902 lok_doc_view_post_command (LOKDocView* pDocView,
3903 const gchar* pCommand,
3904 const gchar* pArguments,
3905 gboolean bNotifyWhenFinished)
3907 LOKDocViewPrivate& priv = getPrivate(pDocView);
3908 if (!priv->m_pDocument)
3909 return;
3911 if (priv->m_bEdit)
3912 LOKPostCommand(pDocView, pCommand, pArguments, bNotifyWhenFinished);
3913 else
3914 g_info ("LOK_POST_COMMAND: ignoring commands in view-only mode");
3917 SAL_DLLPUBLIC_EXPORT gchar *
3918 lok_doc_view_get_command_values (LOKDocView* pDocView,
3919 const gchar* pCommand)
3921 g_return_val_if_fail (LOK_IS_DOC_VIEW (pDocView), nullptr);
3922 g_return_val_if_fail (pCommand != nullptr, nullptr);
3924 LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(pDocView);
3925 if (!pDocument)
3926 return nullptr;
3928 return pDocument->pClass->getCommandValues(pDocument, pCommand);
3931 SAL_DLLPUBLIC_EXPORT void
3932 lok_doc_view_find_prev (LOKDocView* pDocView,
3933 const gchar* pText,
3934 gboolean bHighlightAll)
3936 doSearch(pDocView, pText, true, bHighlightAll);
3939 SAL_DLLPUBLIC_EXPORT void
3940 lok_doc_view_find_next (LOKDocView* pDocView,
3941 const gchar* pText,
3942 gboolean bHighlightAll)
3944 doSearch(pDocView, pText, false, bHighlightAll);
3947 SAL_DLLPUBLIC_EXPORT void
3948 lok_doc_view_highlight_all (LOKDocView* pDocView,
3949 const gchar* pText)
3951 doSearch(pDocView, pText, false, true);
3954 SAL_DLLPUBLIC_EXPORT gchar*
3955 lok_doc_view_copy_selection (LOKDocView* pDocView,
3956 const gchar* pMimeType,
3957 gchar** pUsedMimeType)
3959 LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(pDocView);
3960 if (!pDocument)
3961 return nullptr;
3963 std::stringstream ss;
3964 ss << "lok::Document::getTextSelection('" << pMimeType << "')";
3965 g_info("%s", ss.str().c_str());
3966 return pDocument->pClass->getTextSelection(pDocument, pMimeType, pUsedMimeType);
3969 SAL_DLLPUBLIC_EXPORT gboolean
3970 lok_doc_view_paste (LOKDocView* pDocView,
3971 const gchar* pMimeType,
3972 const gchar* pData,
3973 gsize nSize)
3975 LOKDocViewPrivate& priv = getPrivate(pDocView);
3976 LibreOfficeKitDocument* pDocument = priv->m_pDocument;
3977 bool ret = false;
3979 if (!pDocument)
3980 return false;
3982 if (!priv->m_bEdit)
3984 g_info ("ignoring paste in view-only mode");
3985 return ret;
3988 if (pData)
3990 std::stringstream ss;
3991 ss << "lok::Document::paste('" << pMimeType << "', '" << std::string(pData, nSize) << ", "<<nSize<<"')";
3992 g_info("%s", ss.str().c_str());
3993 ret = pDocument->pClass->paste(pDocument, pMimeType, pData, nSize);
3996 return ret;
3999 SAL_DLLPUBLIC_EXPORT void
4000 lok_doc_view_set_document_password (LOKDocView* pDocView,
4001 const gchar* pURL,
4002 const gchar* pPassword)
4004 LOKDocViewPrivate& priv = getPrivate(pDocView);
4006 priv->m_pOffice->pClass->setDocumentPassword(priv->m_pOffice, pURL, pPassword);
4009 SAL_DLLPUBLIC_EXPORT gchar*
4010 lok_doc_view_get_version_info (LOKDocView* pDocView)
4012 LOKDocViewPrivate& priv = getPrivate(pDocView);
4014 return priv->m_pOffice->pClass->getVersionInfo(priv->m_pOffice);
4018 SAL_DLLPUBLIC_EXPORT gfloat
4019 lok_doc_view_pixel_to_twip (LOKDocView* pDocView, float fInput)
4021 LOKDocViewPrivate& priv = getPrivate(pDocView);
4022 return pixelToTwip(fInput, priv->m_fZoom);
4025 SAL_DLLPUBLIC_EXPORT gfloat
4026 lok_doc_view_twip_to_pixel (LOKDocView* pDocView, float fInput)
4028 LOKDocViewPrivate& priv = getPrivate(pDocView);
4029 return twipToPixel(fInput, priv->m_fZoom);
4032 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */