Version 5.4.3.2, tag libreoffice-5.4.3.2
[LibreOffice.git] / libreofficekit / source / gtk / lokdocview.cxx
blobc42132acf8ad1ec8c01f146c64af08e9cc49fa0b
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <sal/types.h>
11 #include <math.h>
12 #include <string.h>
13 #include <memory>
14 #include <vector>
15 #include <string>
16 #include <sstream>
17 #include <iostream>
18 #include <mutex>
19 #include <boost/property_tree/json_parser.hpp>
21 #include <com/sun/star/awt/Key.hpp>
22 #include <LibreOfficeKit/LibreOfficeKit.h>
23 #include <LibreOfficeKit/LibreOfficeKitInit.h>
24 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
25 #include <LibreOfficeKit/LibreOfficeKitGtk.h>
26 #include <rsc/rsc-vcl-shared-types.hxx>
27 #include <vcl/event.hxx>
29 #include "tilebuffer.hxx"
31 #if !GLIB_CHECK_VERSION(2,32,0)
32 #define G_SOURCE_REMOVE FALSE
33 #define G_SOURCE_CONTINUE TRUE
34 #endif
35 #if !GLIB_CHECK_VERSION(2,40,0)
36 #define g_info(...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__)
37 #endif
39 // Cursor bitmaps from the installation set.
40 #define CURSOR_HANDLE_DIR "/../share/libreofficekit/"
41 // Number of handles around a graphic selection.
42 #define GRAPHIC_HANDLE_COUNT 8
43 // Maximum Zoom allowed
44 #define MAX_ZOOM 5.0f
45 // Minimum Zoom allowed
46 #define MIN_ZOOM 0.25f
48 /// This is expected to be locked during setView(), doSomethingElse() LOK calls.
49 static std::mutex g_aLOKMutex;
51 /// Same as a GdkRectangle, but also tracks in which part the rectangle is.
52 struct ViewRectangle
54 int m_nPart;
55 GdkRectangle m_aRectangle;
57 ViewRectangle(int nPart = 0, const GdkRectangle& rRectangle = GdkRectangle())
58 : m_nPart(nPart),
59 m_aRectangle(rRectangle)
64 /// Same as a list of GdkRectangles, but also tracks in which part the rectangle is.
65 struct ViewRectangles
67 int m_nPart;
68 std::vector<GdkRectangle> m_aRectangles;
70 ViewRectangles(int nPart = 0, const std::vector<GdkRectangle>& rRectangles = std::vector<GdkRectangle>())
71 : m_nPart(nPart),
72 m_aRectangles(rRectangles)
77 /// Private struct used by this GObject type
78 struct LOKDocViewPrivateImpl
80 const gchar* m_aLOPath;
81 const gchar* m_pUserProfileURL;
82 const gchar* m_aDocPath;
83 std::string m_aRenderingArguments;
84 gdouble m_nLoadProgress;
85 gboolean m_bIsLoading;
86 gboolean m_bCanZoomIn;
87 gboolean m_bCanZoomOut;
88 LibreOfficeKit* m_pOffice;
89 LibreOfficeKitDocument* m_pDocument;
91 std::unique_ptr<TileBuffer> m_pTileBuffer;
92 GThreadPool* lokThreadPool;
94 gfloat m_fZoom;
95 glong m_nDocumentWidthTwips;
96 glong m_nDocumentHeightTwips;
97 /// View or edit mode.
98 gboolean m_bEdit;
99 /// LOK Features
100 guint64 m_nLOKFeatures;
101 /// Number of parts in currently loaded document
102 gint m_nParts;
103 /// Position and size of the visible cursor.
104 GdkRectangle m_aVisibleCursor;
105 /// Position and size of the view cursors. The current view can only see
106 /// them, can't modify them. Key is the view id.
107 std::map<int, ViewRectangle> m_aViewCursors;
108 /// Cursor overlay is visible or hidden (for blinking).
109 gboolean m_bCursorOverlayVisible;
110 /// Cursor is visible or hidden (e.g. for graphic selection).
111 gboolean m_bCursorVisible;
112 /// Visibility of view selections. The current view can only see / them,
113 /// can't modify them. Key is the view id.
114 std::map<int, bool> m_aViewCursorVisibilities;
115 /// Time of the last button press.
116 guint32 m_nLastButtonPressTime;
117 /// Time of the last button release.
118 guint32 m_nLastButtonReleaseTime;
119 /// Last pressed button (left, right, middle)
120 guint32 m_nLastButtonPressed;
121 /// Key modifier (ctrl, atl, shift)
122 guint32 m_nKeyModifier;
123 /// Rectangles of the current text selection.
124 std::vector<GdkRectangle> m_aTextSelectionRectangles;
125 /// Rectangles of view selections. The current view can only see
126 /// them, can't modify them. Key is the view id.
127 std::map<int, ViewRectangles> m_aTextViewSelectionRectangles;
128 /// Position and size of the selection start (as if there would be a cursor caret there).
129 GdkRectangle m_aTextSelectionStart;
130 /// Position and size of the selection end.
131 GdkRectangle m_aTextSelectionEnd;
132 GdkRectangle m_aGraphicSelection;
133 /// Position and size of the graphic view selections. The current view can only
134 /// see them, can't modify them. Key is the view id.
135 std::map<int, ViewRectangle> m_aGraphicViewSelections;
136 GdkRectangle m_aCellCursor;
137 /// Position and size of the cell view cursors. The current view can only
138 /// see them, can't modify them. Key is the view id.
139 std::map<int, ViewRectangle> m_aCellViewCursors;
140 gboolean m_bInDragGraphicSelection;
142 /// @name Start/middle/end handle.
143 ///@{
144 /// Bitmap of the text selection start handle.
145 cairo_surface_t* m_pHandleStart;
146 /// Rectangle of the text selection start handle, to know if the user clicked on it or not
147 GdkRectangle m_aHandleStartRect;
148 /// If we are in the middle of a drag of the text selection end handle.
149 gboolean m_bInDragStartHandle;
150 /// Bitmap of the text selection middle handle.
151 cairo_surface_t* m_pHandleMiddle;
152 /// Rectangle of the text selection middle handle, to know if the user clicked on it or not
153 GdkRectangle m_aHandleMiddleRect;
154 /// If we are in the middle of a drag of the text selection middle handle.
155 gboolean m_bInDragMiddleHandle;
156 /// Bitmap of the text selection end handle.
157 cairo_surface_t* m_pHandleEnd;
158 /// Rectangle of the text selection end handle, to know if the user clicked on it or not
159 GdkRectangle m_aHandleEndRect;
160 /// If we are in the middle of a drag of the text selection end handle.
161 gboolean m_bInDragEndHandle;
162 ///@}
164 /// @name Graphic handles.
165 ///@{
166 /// Rectangle of a graphic selection handle, to know if the user clicked on it or not.
167 GdkRectangle m_aGraphicHandleRects[8];
168 /// If we are in the middle of a drag of a graphic selection handle.
169 gboolean m_bInDragGraphicHandles[8];
170 ///@}
172 /// View ID, returned by createView() or 0 by default.
173 int m_nViewId;
175 /// Cached part ID, returned by getPart().
176 int m_nPartId;
178 /// Cached document type, returned by getDocumentType().
179 LibreOfficeKitDocumentType m_eDocumentType;
182 * Contains a freshly set zoom level: logic size of a tile.
183 * It gets reset back to 0 when LOK was informed about this zoom change.
185 int m_nTileSizeTwips;
187 GdkRectangle m_aVisibleArea;
188 bool m_bVisibleAreaSet;
190 /// Event source ID for handleTimeout() of this widget.
191 guint m_nTimeoutId;
193 /// Rectangles of view locks. The current view can only see
194 /// them, can't modify them. Key is the view id.
195 std::map<int, ViewRectangle> m_aViewLockRectangles;
197 LOKDocViewPrivateImpl()
198 : m_aLOPath(nullptr),
199 m_pUserProfileURL(nullptr),
200 m_aDocPath(nullptr),
201 m_nLoadProgress(0),
202 m_bIsLoading(false),
203 m_bCanZoomIn(true),
204 m_bCanZoomOut(true),
205 m_pOffice(nullptr),
206 m_pDocument(nullptr),
207 lokThreadPool(nullptr),
208 m_fZoom(0),
209 m_nDocumentWidthTwips(0),
210 m_nDocumentHeightTwips(0),
211 m_bEdit(FALSE),
212 m_nLOKFeatures(0),
213 m_nParts(0),
214 m_aVisibleCursor({0, 0, 0, 0}),
215 m_bCursorOverlayVisible(false),
216 m_bCursorVisible(true),
217 m_nLastButtonPressTime(0),
218 m_nLastButtonReleaseTime(0),
219 m_nLastButtonPressed(0),
220 m_nKeyModifier(0),
221 m_aTextSelectionStart({0, 0, 0, 0}),
222 m_aTextSelectionEnd({0, 0, 0, 0}),
223 m_aGraphicSelection({0, 0, 0, 0}),
224 m_aCellCursor({0, 0, 0, 0}),
225 m_bInDragGraphicSelection(false),
226 m_pHandleStart(nullptr),
227 m_aHandleStartRect({0, 0, 0, 0}),
228 m_bInDragStartHandle(0),
229 m_pHandleMiddle(nullptr),
230 m_aHandleMiddleRect({0, 0, 0, 0}),
231 m_bInDragMiddleHandle(false),
232 m_pHandleEnd(nullptr),
233 m_aHandleEndRect({0, 0, 0, 0}),
234 m_bInDragEndHandle(false),
235 m_nViewId(0),
236 m_nPartId(0),
237 m_eDocumentType(LOK_DOCTYPE_OTHER),
238 m_nTileSizeTwips(0),
239 m_aVisibleArea({0, 0, 0, 0}),
240 m_bVisibleAreaSet(false),
241 m_nTimeoutId(0)
243 memset(&m_aGraphicHandleRects, 0, sizeof(m_aGraphicHandleRects));
244 memset(&m_bInDragGraphicHandles, 0, sizeof(m_bInDragGraphicHandles));
247 ~LOKDocViewPrivateImpl()
249 g_source_remove(m_nTimeoutId);
253 /// Wrapper around LOKDocViewPrivateImpl, managed by malloc/memset/free.
254 struct _LOKDocViewPrivate
256 LOKDocViewPrivateImpl* m_pImpl;
258 LOKDocViewPrivateImpl* operator->()
260 return m_pImpl;
264 enum
266 LOAD_CHANGED,
267 EDIT_CHANGED,
268 COMMAND_CHANGED,
269 SEARCH_NOT_FOUND,
270 PART_CHANGED,
271 SIZE_CHANGED,
272 HYPERLINK_CLICKED,
273 CURSOR_CHANGED,
274 SEARCH_RESULT_COUNT,
275 COMMAND_RESULT,
276 ADDRESS_CHANGED,
277 FORMULA_CHANGED,
278 TEXT_SELECTION,
279 PASSWORD_REQUIRED,
280 COMMENT,
282 LAST_SIGNAL
285 enum
287 PROP_0,
289 PROP_LO_PATH,
290 PROP_LO_POINTER,
291 PROP_USER_PROFILE_URL,
292 PROP_DOC_PATH,
293 PROP_DOC_POINTER,
294 PROP_EDITABLE,
295 PROP_LOAD_PROGRESS,
296 PROP_ZOOM,
297 PROP_IS_LOADING,
298 PROP_DOC_WIDTH,
299 PROP_DOC_HEIGHT,
300 PROP_CAN_ZOOM_IN,
301 PROP_CAN_ZOOM_OUT,
302 PROP_DOC_PASSWORD,
303 PROP_DOC_PASSWORD_TO_MODIFY,
304 PROP_TILED_ANNOTATIONS,
306 PROP_LAST
309 static guint doc_view_signals[LAST_SIGNAL] = { 0 };
310 static GParamSpec *properties[PROP_LAST] = { nullptr };
312 static void lok_doc_view_initable_iface_init (GInitableIface *iface);
313 static void callbackWorker (int nType, const char* pPayload, void* pData);
315 SAL_DLLPUBLIC_EXPORT GType lok_doc_view_get_type();
316 #ifdef __GNUC__
317 #pragma GCC diagnostic push
318 #pragma GCC diagnostic ignored "-Wunused-function"
319 #endif
320 G_DEFINE_TYPE_WITH_CODE (LOKDocView, lok_doc_view, GTK_TYPE_DRAWING_AREA,
321 G_ADD_PRIVATE (LOKDocView)
322 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, lok_doc_view_initable_iface_init));
323 #ifdef __GNUC__
324 #pragma GCC diagnostic pop
325 #endif
327 static LOKDocViewPrivate& getPrivate(LOKDocView* pDocView)
329 LOKDocViewPrivate* priv = static_cast<LOKDocViewPrivate*>(lok_doc_view_get_instance_private(pDocView));
330 return *priv;
333 /// Helper struct used to pass the data from soffice thread -> main thread.
334 struct CallbackData
336 int m_nType;
337 std::string m_aPayload;
338 LOKDocView* m_pDocView;
340 CallbackData(int nType, const std::string& rPayload, LOKDocView* pDocView)
341 : m_nType(nType),
342 m_aPayload(rPayload),
343 m_pDocView(pDocView) {}
346 static void
347 payloadToSize(const char* pPayload, long& rWidth, long& rHeight)
349 rWidth = rHeight = 0;
350 gchar** ppCoordinates = g_strsplit(pPayload, ", ", 2);
351 gchar** ppCoordinate = ppCoordinates;
352 if (!*ppCoordinate)
353 return;
354 rWidth = atoi(*ppCoordinate);
355 ++ppCoordinate;
356 if (!*ppCoordinate)
357 return;
358 rHeight = atoi(*ppCoordinate);
359 g_strfreev(ppCoordinates);
362 /// Returns the string representation of a LibreOfficeKitCallbackType enumeration element.
363 static const char*
364 callbackTypeToString (int nType)
366 switch (nType)
368 case LOK_CALLBACK_INVALIDATE_TILES:
369 return "LOK_CALLBACK_INVALIDATE_TILES";
370 case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
371 return "LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR";
372 case LOK_CALLBACK_TEXT_SELECTION:
373 return "LOK_CALLBACK_TEXT_SELECTION";
374 case LOK_CALLBACK_TEXT_SELECTION_START:
375 return "LOK_CALLBACK_TEXT_SELECTION_START";
376 case LOK_CALLBACK_TEXT_SELECTION_END:
377 return "LOK_CALLBACK_TEXT_SELECTION_END";
378 case LOK_CALLBACK_CURSOR_VISIBLE:
379 return "LOK_CALLBACK_CURSOR_VISIBLE";
380 case LOK_CALLBACK_VIEW_CURSOR_VISIBLE:
381 return "LOK_CALLBACK_VIEW_CURSOR_VISIBLE";
382 case LOK_CALLBACK_GRAPHIC_SELECTION:
383 return "LOK_CALLBACK_GRAPHIC_SELECTION";
384 case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
385 return "LOK_CALLBACK_GRAPHIC_VIEW_SELECTION";
386 case LOK_CALLBACK_CELL_CURSOR:
387 return "LOK_CALLBACK_CELL_CURSOR";
388 case LOK_CALLBACK_HYPERLINK_CLICKED:
389 return "LOK_CALLBACK_HYPERLINK_CLICKED";
390 case LOK_CALLBACK_MOUSE_POINTER:
391 return "LOK_CALLBACK_MOUSE_POINTER";
392 case LOK_CALLBACK_STATE_CHANGED:
393 return "LOK_CALLBACK_STATE_CHANGED";
394 case LOK_CALLBACK_STATUS_INDICATOR_START:
395 return "LOK_CALLBACK_STATUS_INDICATOR_START";
396 case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
397 return "LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE";
398 case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
399 return "LOK_CALLBACK_STATUS_INDICATOR_FINISH";
400 case LOK_CALLBACK_SEARCH_NOT_FOUND:
401 return "LOK_CALLBACK_SEARCH_NOT_FOUND";
402 case LOK_CALLBACK_DOCUMENT_SIZE_CHANGED:
403 return "LOK_CALLBACK_DOCUMENT_SIZE_CHANGED";
404 case LOK_CALLBACK_SET_PART:
405 return "LOK_CALLBACK_SET_PART";
406 case LOK_CALLBACK_SEARCH_RESULT_SELECTION:
407 return "LOK_CALLBACK_SEARCH_RESULT_SELECTION";
408 case LOK_CALLBACK_DOCUMENT_PASSWORD:
409 return "LOK_CALLBACK_DOCUMENT_PASSWORD";
410 case LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY:
411 return "LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY";
412 case LOK_CALLBACK_CONTEXT_MENU:
413 return "LOK_CALLBACK_CONTEXT_MENU";
414 case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
415 return "LOK_CALLBACK_INVALIDATE_VIEW_CURSOR";
416 case LOK_CALLBACK_TEXT_VIEW_SELECTION:
417 return "LOK_CALLBACK_TEXT_VIEW_SELECTION";
418 case LOK_CALLBACK_CELL_VIEW_CURSOR:
419 return "LOK_CALLBACK_CELL_VIEW_CURSOR";
420 case LOK_CALLBACK_CELL_ADDRESS:
421 return "LOK_CALLBACK_CELL_ADDRESS";
422 case LOK_CALLBACK_CELL_FORMULA:
423 return "LOK_CALLBACK_CELL_FORMULA";
424 case LOK_CALLBACK_UNO_COMMAND_RESULT:
425 return "LOK_CALLBACK_UNO_COMMAND_RESULT";
426 case LOK_CALLBACK_ERROR:
427 return "LOK_CALLBACK_ERROR";
428 case LOK_CALLBACK_VIEW_LOCK:
429 return "LOK_CALLBACK_VIEW_LOCK";
430 case LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED:
431 return "LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED";
432 case LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED:
433 return "LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED";
434 case LOK_CALLBACK_COMMENT:
435 return "LOK_CALLBACK_COMMENT";
437 g_assert(false);
438 return nullptr;
441 static void
442 LOKPostCommand (LOKDocView* pDocView,
443 const gchar* pCommand,
444 const gchar* pArguments,
445 gboolean bNotifyWhenFinished)
447 LOKDocViewPrivate& priv = getPrivate(pDocView);
448 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
449 LOEvent* pLOEvent = new LOEvent(LOK_POST_COMMAND);
450 GError* error = nullptr;
451 pLOEvent->m_pCommand = g_strdup(pCommand);
452 pLOEvent->m_pArguments = g_strdup(pArguments);
453 pLOEvent->m_bNotifyWhenFinished = bNotifyWhenFinished;
455 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
456 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
457 if (error != nullptr)
459 g_warning("Unable to call LOK_POST_COMMAND: %s", error->message);
460 g_clear_error(&error);
462 g_object_unref(task);
465 static void
466 doSearch(LOKDocView* pDocView, const char* pText, bool bBackwards, bool highlightAll)
468 LOKDocViewPrivate& priv = getPrivate(pDocView);
469 if (!priv->m_pDocument)
470 return;
472 boost::property_tree::ptree aTree;
473 GtkWidget* drawingWidget = GTK_WIDGET(pDocView);
474 GdkWindow* drawingWindow = gtk_widget_get_window(drawingWidget);
475 if (!drawingWindow)
476 return;
477 std::shared_ptr<cairo_region_t> cairoVisRegion( gdk_window_get_visible_region(drawingWindow),
478 cairo_region_destroy);
479 cairo_rectangle_int_t cairoVisRect;
480 cairo_region_get_rectangle(cairoVisRegion.get(), 0, &cairoVisRect);
481 int x = pixelToTwip (cairoVisRect.x, priv->m_fZoom);
482 int y = pixelToTwip (cairoVisRect.y, priv->m_fZoom);
484 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchString/type", '/'), "string");
485 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchString/value", '/'), pText);
486 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Backward/type", '/'), "boolean");
487 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Backward/value", '/'), bBackwards);
488 if (highlightAll)
490 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Command/type", '/'), "unsigned short");
491 // SvxSearchCmd::FIND_ALL
492 aTree.put(boost::property_tree::ptree::path_type("SearchItem.Command/value", '/'), "1");
495 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointX/type", '/'), "long");
496 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointX/value", '/'), x);
497 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointY/type", '/'), "long");
498 aTree.put(boost::property_tree::ptree::path_type("SearchItem.SearchStartPointY/value", '/'), y);
500 std::stringstream aStream;
501 boost::property_tree::write_json(aStream, aTree);
503 LOKPostCommand (pDocView, ".uno:ExecuteSearch", aStream.str().c_str(), false);
506 static bool
507 isEmptyRectangle(const GdkRectangle& rRectangle)
509 return rRectangle.x == 0 && rRectangle.y == 0 && rRectangle.width == 0 && rRectangle.height == 0;
512 /// if handled, returns TRUE else FALSE
513 static bool
514 handleTextSelectionOnButtonPress(GdkRectangle& aClick, LOKDocView* pDocView) {
515 LOKDocViewPrivate& priv = getPrivate(pDocView);
517 if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleStartRect, nullptr))
519 g_info("LOKDocView_Impl::signalButton: start of drag start handle");
520 priv->m_bInDragStartHandle = true;
521 return TRUE;
523 else if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleMiddleRect, nullptr))
525 g_info("LOKDocView_Impl::signalButton: start of drag middle handle");
526 priv->m_bInDragMiddleHandle = true;
527 return TRUE;
529 else if (gdk_rectangle_intersect(&aClick, &priv->m_aHandleEndRect, nullptr))
531 g_info("LOKDocView_Impl::signalButton: start of drag end handle");
532 priv->m_bInDragEndHandle = true;
533 return TRUE;
536 return FALSE;
539 /// if handled, returns TRUE else FALSE
540 static bool
541 handleGraphicSelectionOnButtonPress(GdkRectangle& aClick, LOKDocView* pDocView) {
542 LOKDocViewPrivate& priv = getPrivate(pDocView);
543 GError* error = nullptr;
545 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
547 if (gdk_rectangle_intersect(&aClick, &priv->m_aGraphicHandleRects[i], nullptr))
549 g_info("LOKDocView_Impl::signalButton: start of drag graphic handle #%d", i);
550 priv->m_bInDragGraphicHandles[i] = true;
552 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
553 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
554 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_START;
555 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(priv->m_aGraphicHandleRects[i].x + priv->m_aGraphicHandleRects[i].width / 2, priv->m_fZoom);
556 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(priv->m_aGraphicHandleRects[i].y + priv->m_aGraphicHandleRects[i].height / 2, priv->m_fZoom);
557 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
559 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
560 if (error != nullptr)
562 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
563 g_clear_error(&error);
565 g_object_unref(task);
567 return TRUE;
571 return FALSE;
574 /// if handled, returns TRUE else FALSE
575 static bool
576 handleTextSelectionOnButtonRelease(LOKDocView* pDocView) {
577 LOKDocViewPrivate& priv = getPrivate(pDocView);
579 if (priv->m_bInDragStartHandle)
581 g_info("LOKDocView_Impl::signalButton: end of drag start handle");
582 priv->m_bInDragStartHandle = false;
583 return TRUE;
585 else if (priv->m_bInDragMiddleHandle)
587 g_info("LOKDocView_Impl::signalButton: end of drag middle handle");
588 priv->m_bInDragMiddleHandle = false;
589 return TRUE;
591 else if (priv->m_bInDragEndHandle)
593 g_info("LOKDocView_Impl::signalButton: end of drag end handle");
594 priv->m_bInDragEndHandle = false;
595 return TRUE;
598 return FALSE;
601 /// if handled, returns TRUE else FALSE
602 static bool
603 handleGraphicSelectionOnButtonRelease(LOKDocView* pDocView, GdkEventButton* pEvent) {
604 LOKDocViewPrivate& priv = getPrivate(pDocView);
605 GError* error = nullptr;
607 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
609 if (priv->m_bInDragGraphicHandles[i])
611 g_info("LOKDocView_Impl::signalButton: end of drag graphic handle #%d", i);
612 priv->m_bInDragGraphicHandles[i] = false;
614 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
615 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
616 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_END;
617 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(pEvent->x, priv->m_fZoom);
618 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(pEvent->y, priv->m_fZoom);
619 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
621 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
622 if (error != nullptr)
624 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
625 g_clear_error(&error);
627 g_object_unref(task);
629 return TRUE;
633 if (priv->m_bInDragGraphicSelection)
635 g_info("LOKDocView_Impl::signalButton: end of drag graphic selection");
636 priv->m_bInDragGraphicSelection = false;
638 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
639 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
640 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_END;
641 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(pEvent->x, priv->m_fZoom);
642 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(pEvent->y, priv->m_fZoom);
643 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
645 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
646 if (error != nullptr)
648 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
649 g_clear_error(&error);
651 g_object_unref(task);
653 return TRUE;
656 return FALSE;
659 static void
660 postKeyEventInThread(gpointer data)
662 GTask* task = G_TASK(data);
663 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
664 LOKDocViewPrivate& priv = getPrivate(pDocView);
665 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
667 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
668 std::stringstream ss;
669 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
670 g_info("%s", ss.str().c_str());
671 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
673 if (priv->m_nTileSizeTwips)
675 ss.str(std::string());
676 ss << "lok::Document::setClientZoom(" << nTileSizePixels << ", " << nTileSizePixels << ", " << priv->m_nTileSizeTwips << ", " << priv->m_nTileSizeTwips << ")";
677 g_info("%s", ss.str().c_str());
678 priv->m_pDocument->pClass->setClientZoom(priv->m_pDocument,
679 nTileSizePixels,
680 nTileSizePixels,
681 priv->m_nTileSizeTwips,
682 priv->m_nTileSizeTwips);
683 priv->m_nTileSizeTwips = 0;
685 if (priv->m_bVisibleAreaSet)
687 ss.str(std::string());
688 ss << "lok::Document::setClientVisibleArea(" << priv->m_aVisibleArea.x << ", " << priv->m_aVisibleArea.y << ", ";
689 ss << priv->m_aVisibleArea.width << ", " << priv->m_aVisibleArea.height << ")";
690 g_info("%s", ss.str().c_str());
691 priv->m_pDocument->pClass->setClientVisibleArea(priv->m_pDocument,
692 priv->m_aVisibleArea.x,
693 priv->m_aVisibleArea.y,
694 priv->m_aVisibleArea.width,
695 priv->m_aVisibleArea.height);
696 priv->m_bVisibleAreaSet = false;
699 ss.str(std::string());
700 ss << "lok::Document::postKeyEvent(" << pLOEvent->m_nKeyEvent << ", " << pLOEvent->m_nCharCode << ", " << pLOEvent->m_nKeyCode << ")";
701 g_info("%s", ss.str().c_str());
702 priv->m_pDocument->pClass->postKeyEvent(priv->m_pDocument,
703 pLOEvent->m_nKeyEvent,
704 pLOEvent->m_nCharCode,
705 pLOEvent->m_nKeyCode);
708 static gboolean
709 signalKey (GtkWidget* pWidget, GdkEventKey* pEvent)
711 LOKDocView* pDocView = LOK_DOC_VIEW(pWidget);
712 LOKDocViewPrivate& priv = getPrivate(pDocView);
713 int nCharCode = 0;
714 int nKeyCode = 0;
715 GError* error = nullptr;
717 if (!priv->m_bEdit)
719 g_info("signalKey: not in edit mode, ignore");
720 return FALSE;
723 priv->m_nKeyModifier &= KEY_MOD2;
724 switch (pEvent->keyval)
726 case GDK_KEY_BackSpace:
727 nKeyCode = com::sun::star::awt::Key::BACKSPACE;
728 break;
729 case GDK_KEY_Delete:
730 nKeyCode = com::sun::star::awt::Key::DELETE;
731 break;
732 case GDK_KEY_Return:
733 case GDK_KEY_KP_Enter:
734 nKeyCode = com::sun::star::awt::Key::RETURN;
735 break;
736 case GDK_KEY_Escape:
737 nKeyCode = com::sun::star::awt::Key::ESCAPE;
738 break;
739 case GDK_KEY_Tab:
740 nKeyCode = com::sun::star::awt::Key::TAB;
741 break;
742 case GDK_KEY_Down:
743 nKeyCode = com::sun::star::awt::Key::DOWN;
744 break;
745 case GDK_KEY_Up:
746 nKeyCode = com::sun::star::awt::Key::UP;
747 break;
748 case GDK_KEY_Left:
749 nKeyCode = com::sun::star::awt::Key::LEFT;
750 break;
751 case GDK_KEY_Right:
752 nKeyCode = com::sun::star::awt::Key::RIGHT;
753 break;
754 case GDK_KEY_Page_Down:
755 nKeyCode = com::sun::star::awt::Key::PAGEDOWN;
756 break;
757 case GDK_KEY_Page_Up:
758 nKeyCode = com::sun::star::awt::Key::PAGEUP;
759 break;
760 case GDK_KEY_Insert:
761 nKeyCode = com::sun::star::awt::Key::INSERT;
762 break;
763 case GDK_KEY_Shift_L:
764 case GDK_KEY_Shift_R:
765 if (pEvent->type == GDK_KEY_PRESS)
766 priv->m_nKeyModifier |= KEY_SHIFT;
767 break;
768 case GDK_KEY_Control_L:
769 case GDK_KEY_Control_R:
770 if (pEvent->type == GDK_KEY_PRESS)
771 priv->m_nKeyModifier |= KEY_MOD1;
772 break;
773 case GDK_KEY_Alt_L:
774 case GDK_KEY_Alt_R:
775 if (pEvent->type == GDK_KEY_PRESS)
776 priv->m_nKeyModifier |= KEY_MOD2;
777 else
778 priv->m_nKeyModifier &= ~KEY_MOD2;
779 break;
780 default:
781 if (pEvent->keyval >= GDK_KEY_F1 && pEvent->keyval <= GDK_KEY_F26)
782 nKeyCode = com::sun::star::awt::Key::F1 + (pEvent->keyval - GDK_KEY_F1);
783 else
784 nCharCode = gdk_keyval_to_unicode(pEvent->keyval);
787 // rsc is not public API, but should be good enough for debugging purposes.
788 // If this is needed for real, then probably a new param of type
789 // css::awt::KeyModifier is needed in postKeyEvent().
790 if (pEvent->state & GDK_SHIFT_MASK)
791 nKeyCode |= KEY_SHIFT;
793 if (pEvent->state & GDK_CONTROL_MASK)
794 nKeyCode |= KEY_MOD1;
796 if (priv->m_nKeyModifier & KEY_MOD2)
797 nKeyCode |= KEY_MOD2;
799 if (nKeyCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2)) {
800 if (pEvent->keyval >= GDK_KEY_a && pEvent->keyval <= GDK_KEY_z)
802 nKeyCode |= 512 + (pEvent->keyval - GDK_KEY_a);
804 else if (pEvent->keyval >= GDK_KEY_A && pEvent->keyval <= GDK_KEY_Z) {
805 nKeyCode |= 512 + (pEvent->keyval - GDK_KEY_A);
807 else if (pEvent->keyval >= GDK_KEY_0 && pEvent->keyval <= GDK_KEY_9) {
808 nKeyCode |= 256 + (pEvent->keyval - GDK_KEY_0);
812 if (pEvent->type == GDK_KEY_RELEASE)
814 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
815 LOEvent* pLOEvent = new LOEvent(LOK_POST_KEY);
816 pLOEvent->m_nKeyEvent = LOK_KEYEVENT_KEYUP;
817 pLOEvent->m_nCharCode = nCharCode;
818 pLOEvent->m_nKeyCode = nKeyCode;
819 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
820 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
821 if (error != nullptr)
823 g_warning("Unable to call LOK_POST_KEY: %s", error->message);
824 g_clear_error(&error);
826 g_object_unref(task);
828 else
830 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
831 LOEvent* pLOEvent = new LOEvent(LOK_POST_KEY);
832 pLOEvent->m_nKeyEvent = LOK_KEYEVENT_KEYINPUT;
833 pLOEvent->m_nCharCode = nCharCode;
834 pLOEvent->m_nKeyCode = nKeyCode;
835 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
836 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
837 if (error != nullptr)
839 g_warning("Unable to call LOK_POST_KEY: %s", error->message);
840 g_clear_error(&error);
842 g_object_unref(task);
845 return FALSE;
848 static gboolean
849 handleTimeout (gpointer pData)
851 LOKDocView* pDocView = LOK_DOC_VIEW (pData);
852 LOKDocViewPrivate& priv = getPrivate(pDocView);
854 if (priv->m_bEdit)
856 if (priv->m_bCursorOverlayVisible)
857 priv->m_bCursorOverlayVisible = false;
858 else
859 priv->m_bCursorOverlayVisible = true;
860 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
863 return G_SOURCE_CONTINUE;
866 static void
867 commandChanged(LOKDocView* pDocView, const std::string& rString)
869 g_signal_emit(pDocView, doc_view_signals[COMMAND_CHANGED], 0, rString.c_str());
872 static void
873 searchNotFound(LOKDocView* pDocView, const std::string& rString)
875 g_signal_emit(pDocView, doc_view_signals[SEARCH_NOT_FOUND], 0, rString.c_str());
878 static void searchResultCount(LOKDocView* pDocView, const std::string& rString)
880 g_signal_emit(pDocView, doc_view_signals[SEARCH_RESULT_COUNT], 0, rString.c_str());
883 static void commandResult(LOKDocView* pDocView, const std::string& rString)
885 g_signal_emit(pDocView, doc_view_signals[COMMAND_RESULT], 0, rString.c_str());
888 static void addressChanged(LOKDocView* pDocView, const std::string& rString)
890 g_signal_emit(pDocView, doc_view_signals[ADDRESS_CHANGED], 0, rString.c_str());
893 static void formulaChanged(LOKDocView* pDocView, const std::string& rString)
895 g_signal_emit(pDocView, doc_view_signals[FORMULA_CHANGED], 0, rString.c_str());
898 static void reportError(LOKDocView* /*pDocView*/, const std::string& rString)
900 GtkWidget *dialog = gtk_message_dialog_new(nullptr,
901 GTK_DIALOG_DESTROY_WITH_PARENT,
902 GTK_MESSAGE_ERROR,
903 GTK_BUTTONS_CLOSE,
904 "%s",
905 rString.c_str());
906 gtk_dialog_run(GTK_DIALOG(dialog));
907 gtk_widget_destroy(dialog);
910 static void
911 setPart(LOKDocView* pDocView, const std::string& rString)
913 LOKDocViewPrivate& priv = getPrivate(pDocView);
914 priv->m_nPartId = std::stoi(rString);
915 g_signal_emit(pDocView, doc_view_signals[PART_CHANGED], 0, priv->m_nPartId);
918 static void
919 hyperlinkClicked(LOKDocView* pDocView, const std::string& rString)
921 g_signal_emit(pDocView, doc_view_signals[HYPERLINK_CLICKED], 0, rString.c_str());
924 /// Trigger a redraw, invoked on the main thread by other functions running in a thread.
925 static gboolean queueDraw(gpointer pData)
927 GtkWidget* pWidget = static_cast<GtkWidget*>(pData);
929 gtk_widget_queue_draw(pWidget);
931 return G_SOURCE_REMOVE;
934 /// Looks up the author string from initializeForRendering()'s rendering arguments.
935 static std::string getAuthorRenderingArgument(LOKDocViewPrivate& priv)
937 std::stringstream aStream;
938 aStream << priv->m_aRenderingArguments;
939 boost::property_tree::ptree aTree;
940 boost::property_tree::read_json(aStream, aTree);
941 std::string aRet;
942 for (const std::pair<std::string, boost::property_tree::ptree>& rPair : aTree)
944 if (rPair.first == ".uno:Author")
946 aRet = rPair.second.get<std::string>("value");
947 break;
950 return aRet;
953 /// Author string <-> View ID map
954 static std::map<std::string, int> g_aAuthorViews;
956 /// Set up LOKDocView after the document is loaded, invoked on the main thread by openDocumentInThread() running in a thread.
957 static gboolean postDocumentLoad(gpointer pData)
959 LOKDocView* pLOKDocView = static_cast<LOKDocView*>(pData);
960 LOKDocViewPrivate& priv = getPrivate(pLOKDocView);
962 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
963 priv->m_pDocument->pClass->initializeForRendering(priv->m_pDocument, priv->m_aRenderingArguments.c_str());
964 priv->m_nViewId = priv->m_pDocument->pClass->getView(priv->m_pDocument);
965 g_aAuthorViews[getAuthorRenderingArgument(priv)] = priv->m_nViewId;
966 priv->m_pDocument->pClass->registerCallback(priv->m_pDocument, callbackWorker, pLOKDocView);
967 priv->m_pDocument->pClass->getDocumentSize(priv->m_pDocument, &priv->m_nDocumentWidthTwips, &priv->m_nDocumentHeightTwips);
968 priv->m_nParts = priv->m_pDocument->pClass->getParts(priv->m_pDocument);
969 aGuard.unlock();
970 priv->m_nTimeoutId = g_timeout_add(600, handleTimeout, pLOKDocView);
972 float zoom = priv->m_fZoom;
973 long nDocumentWidthTwips = priv->m_nDocumentWidthTwips;
974 long nDocumentHeightTwips = priv->m_nDocumentHeightTwips;
975 long nDocumentWidthPixels = twipToPixel(nDocumentWidthTwips, zoom);
976 long nDocumentHeightPixels = twipToPixel(nDocumentHeightTwips, zoom);
977 // Total number of columns in this document.
978 guint nColumns = ceil((double)nDocumentWidthPixels / nTileSizePixels);
980 priv->m_pTileBuffer = std::unique_ptr<TileBuffer>(new TileBuffer(nColumns));
981 gtk_widget_set_size_request(GTK_WIDGET(pLOKDocView),
982 nDocumentWidthPixels,
983 nDocumentHeightPixels);
984 gtk_widget_set_can_focus(GTK_WIDGET(pLOKDocView), TRUE);
985 gtk_widget_grab_focus(GTK_WIDGET(pLOKDocView));
986 lok_doc_view_set_zoom(pLOKDocView, 1.0);
988 return G_SOURCE_REMOVE;
991 /// Implementation of the global callback handler, invoked by globalCallback();
992 static gboolean
993 globalCallback (gpointer pData)
995 CallbackData* pCallback = static_cast<CallbackData*>(pData);
996 LOKDocViewPrivate& priv = getPrivate(pCallback->m_pDocView);
997 gboolean bModify = false;
999 switch (pCallback->m_nType)
1001 case LOK_CALLBACK_STATUS_INDICATOR_START:
1003 priv->m_nLoadProgress = 0.0;
1004 g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, 0.0);
1006 break;
1007 case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
1009 priv->m_nLoadProgress = static_cast<gdouble>(std::stoi(pCallback->m_aPayload)/100.0);
1010 g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, priv->m_nLoadProgress);
1012 break;
1013 case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
1015 priv->m_nLoadProgress = 1.0;
1016 g_signal_emit (pCallback->m_pDocView, doc_view_signals[LOAD_CHANGED], 0, 1.0);
1018 break;
1019 case LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY:
1020 bModify = true;
1021 SAL_FALLTHROUGH;
1022 case LOK_CALLBACK_DOCUMENT_PASSWORD:
1024 char const*const pURL(pCallback->m_aPayload.c_str());
1025 g_signal_emit (pCallback->m_pDocView, doc_view_signals[PASSWORD_REQUIRED], 0, pURL, bModify);
1027 break;
1028 case LOK_CALLBACK_ERROR:
1030 reportError(pCallback->m_pDocView, pCallback->m_aPayload);
1032 break;
1033 default:
1034 g_assert(false);
1035 break;
1037 delete pCallback;
1039 return G_SOURCE_REMOVE;
1042 static void
1043 globalCallbackWorker(int nType, const char* pPayload, void* pData)
1045 LOKDocView* pDocView = LOK_DOC_VIEW (pData);
1047 CallbackData* pCallback = new CallbackData(nType, pPayload ? pPayload : "(nil)", pDocView);
1048 g_info("LOKDocView_Impl::globalCallbackWorkerImpl: %s, '%s'", callbackTypeToString(nType), pPayload);
1049 gdk_threads_add_idle(globalCallback, pCallback);
1052 static GdkRectangle
1053 payloadToRectangle (LOKDocView* pDocView, const char* pPayload)
1055 LOKDocViewPrivate& priv = getPrivate(pDocView);
1056 GdkRectangle aRet;
1057 // x, y, width, height, part number.
1058 gchar** ppCoordinates = g_strsplit(pPayload, ", ", 5);
1059 gchar** ppCoordinate = ppCoordinates;
1061 aRet.width = aRet.height = aRet.x = aRet.y = 0;
1063 if (!*ppCoordinate)
1064 return aRet;
1065 aRet.x = atoi(*ppCoordinate);
1066 if (aRet.x < 0)
1067 aRet.x = 0;
1068 ++ppCoordinate;
1069 if (!*ppCoordinate)
1070 return aRet;
1071 aRet.y = atoi(*ppCoordinate);
1072 if (aRet.y < 0)
1073 aRet.y = 0;
1074 ++ppCoordinate;
1075 if (!*ppCoordinate)
1076 return aRet;
1077 long l = atol(*ppCoordinate);
1078 if (l > std::numeric_limits<int>::max())
1079 aRet.width = std::numeric_limits<int>::max();
1080 else
1081 aRet.width = l;
1082 if (aRet.x + aRet.width > priv->m_nDocumentWidthTwips)
1083 aRet.width = priv->m_nDocumentWidthTwips - aRet.x;
1084 ++ppCoordinate;
1085 if (!*ppCoordinate)
1086 return aRet;
1087 l = atol(*ppCoordinate);
1088 if (l > std::numeric_limits<int>::max())
1089 aRet.height = std::numeric_limits<int>::max();
1090 else
1091 aRet.height = l;
1092 if (aRet.y + aRet.height > priv->m_nDocumentHeightTwips)
1093 aRet.height = priv->m_nDocumentHeightTwips - aRet.y;
1094 g_strfreev(ppCoordinates);
1096 return aRet;
1099 static const std::vector<GdkRectangle>
1100 payloadToRectangles(LOKDocView* pDocView, const char* pPayload)
1102 std::vector<GdkRectangle> aRet;
1104 if (g_strcmp0(pPayload, "EMPTY") == 0)
1105 return aRet;
1107 gchar** ppRectangles = g_strsplit(pPayload, "; ", 0);
1108 for (gchar** ppRectangle = ppRectangles; *ppRectangle; ++ppRectangle)
1109 aRet.push_back(payloadToRectangle(pDocView, *ppRectangle));
1110 g_strfreev(ppRectangles);
1112 return aRet;
1116 static void
1117 setTilesInvalid (LOKDocView* pDocView, const GdkRectangle& rRectangle)
1119 LOKDocViewPrivate& priv = getPrivate(pDocView);
1120 GdkRectangle aRectanglePixels;
1121 GdkPoint aStart, aEnd;
1123 aRectanglePixels.x = twipToPixel(rRectangle.x, priv->m_fZoom);
1124 aRectanglePixels.y = twipToPixel(rRectangle.y, priv->m_fZoom);
1125 aRectanglePixels.width = twipToPixel(rRectangle.width, priv->m_fZoom);
1126 aRectanglePixels.height = twipToPixel(rRectangle.height, priv->m_fZoom);
1128 aStart.x = aRectanglePixels.y / nTileSizePixels;
1129 aStart.y = aRectanglePixels.x / nTileSizePixels;
1130 aEnd.x = (aRectanglePixels.y + aRectanglePixels.height + nTileSizePixels) / nTileSizePixels;
1131 aEnd.y = (aRectanglePixels.x + aRectanglePixels.width + nTileSizePixels) / nTileSizePixels;
1132 for (int i = aStart.x; i < aEnd.x; i++)
1134 for (int j = aStart.y; j < aEnd.y; j++)
1136 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
1137 priv->m_pTileBuffer->setInvalid(i, j, priv->m_fZoom, task, priv->lokThreadPool);
1138 g_object_unref(task);
1143 static gboolean
1144 callback (gpointer pData)
1146 CallbackData* pCallback = static_cast<CallbackData*>(pData);
1147 LOKDocView* pDocView = LOK_DOC_VIEW (pCallback->m_pDocView);
1148 LOKDocViewPrivate& priv = getPrivate(pDocView);
1150 //callback registered before the widget was destroyed.
1151 //Use existance of lokThreadPool as flag it was torn down
1152 if (!priv->lokThreadPool)
1154 delete pCallback;
1155 return G_SOURCE_REMOVE;
1158 switch (pCallback->m_nType)
1160 case LOK_CALLBACK_INVALIDATE_TILES:
1162 if (pCallback->m_aPayload.compare(0, 5, "EMPTY") != 0) // payload doesn't start with "EMPTY"
1164 GdkRectangle aRectangle = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1165 setTilesInvalid(pDocView, aRectangle);
1167 else
1168 priv->m_pTileBuffer->resetAllTiles();
1170 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1172 break;
1173 case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
1175 priv->m_aVisibleCursor = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1176 priv->m_bCursorOverlayVisible = true;
1177 g_signal_emit(pDocView, doc_view_signals[CURSOR_CHANGED], 0,
1178 priv->m_aVisibleCursor.x,
1179 priv->m_aVisibleCursor.y,
1180 priv->m_aVisibleCursor.width,
1181 priv->m_aVisibleCursor.height);
1182 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1184 break;
1185 case LOK_CALLBACK_TEXT_SELECTION:
1187 priv->m_aTextSelectionRectangles = payloadToRectangles(pDocView, pCallback->m_aPayload.c_str());
1188 gboolean bIsTextSelected = !priv->m_aTextSelectionRectangles.empty();
1189 // In case the selection is empty, then we get no LOK_CALLBACK_TEXT_SELECTION_START/END events.
1190 if (!bIsTextSelected)
1192 memset(&priv->m_aTextSelectionStart, 0, sizeof(priv->m_aTextSelectionStart));
1193 memset(&priv->m_aHandleStartRect, 0, sizeof(priv->m_aHandleStartRect));
1194 memset(&priv->m_aTextSelectionEnd, 0, sizeof(priv->m_aTextSelectionEnd));
1195 memset(&priv->m_aHandleEndRect, 0, sizeof(priv->m_aHandleEndRect));
1197 else
1198 memset(&priv->m_aHandleMiddleRect, 0, sizeof(priv->m_aHandleMiddleRect));
1200 g_signal_emit(pDocView, doc_view_signals[TEXT_SELECTION], 0, bIsTextSelected);
1201 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1203 break;
1204 case LOK_CALLBACK_TEXT_SELECTION_START:
1206 priv->m_aTextSelectionStart = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1208 break;
1209 case LOK_CALLBACK_TEXT_SELECTION_END:
1211 priv->m_aTextSelectionEnd = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1213 break;
1214 case LOK_CALLBACK_CURSOR_VISIBLE:
1216 priv->m_bCursorVisible = pCallback->m_aPayload == "true";
1218 break;
1219 case LOK_CALLBACK_MOUSE_POINTER:
1221 // We do not want the cursor to get changed in view-only mode
1222 if (priv->m_bEdit)
1224 // The gtk docs claim that most css cursors should be supported, however
1225 // on my system at least this is not true and many cursors are unsupported.
1226 // In this case pCursor = null, which results in the default cursor
1227 // being set.
1228 GdkCursor* pCursor = gdk_cursor_new_from_name(gtk_widget_get_display(GTK_WIDGET(pDocView)),
1229 pCallback->m_aPayload.c_str());
1230 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(pDocView)), pCursor);
1233 break;
1234 case LOK_CALLBACK_GRAPHIC_SELECTION:
1236 if (pCallback->m_aPayload != "EMPTY")
1237 priv->m_aGraphicSelection = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1238 else
1239 memset(&priv->m_aGraphicSelection, 0, sizeof(priv->m_aGraphicSelection));
1240 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1242 break;
1243 case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
1245 std::stringstream aStream(pCallback->m_aPayload);
1246 boost::property_tree::ptree aTree;
1247 boost::property_tree::read_json(aStream, aTree);
1248 int nViewId = aTree.get<int>("viewId");
1249 int nPart = aTree.get<int>("part");
1250 const std::string& rRectangle = aTree.get<std::string>("selection");
1251 if (rRectangle != "EMPTY")
1252 priv->m_aGraphicViewSelections[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1253 else
1255 auto it = priv->m_aGraphicViewSelections.find(nViewId);
1256 if (it != priv->m_aGraphicViewSelections.end())
1257 priv->m_aGraphicViewSelections.erase(it);
1259 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1260 break;
1262 break;
1263 case LOK_CALLBACK_CELL_CURSOR:
1265 if (pCallback->m_aPayload != "EMPTY")
1266 priv->m_aCellCursor = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
1267 else
1268 memset(&priv->m_aCellCursor, 0, sizeof(priv->m_aCellCursor));
1269 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1271 break;
1272 case LOK_CALLBACK_HYPERLINK_CLICKED:
1274 hyperlinkClicked(pDocView, pCallback->m_aPayload);
1276 break;
1277 case LOK_CALLBACK_STATE_CHANGED:
1279 commandChanged(pDocView, pCallback->m_aPayload);
1281 break;
1282 case LOK_CALLBACK_SEARCH_NOT_FOUND:
1284 searchNotFound(pDocView, pCallback->m_aPayload);
1286 break;
1287 case LOK_CALLBACK_DOCUMENT_SIZE_CHANGED:
1289 if (!pCallback->m_aPayload.empty())
1290 payloadToSize(pCallback->m_aPayload.c_str(), priv->m_nDocumentWidthTwips, priv->m_nDocumentHeightTwips);
1291 else
1292 priv->m_pDocument->pClass->getDocumentSize(priv->m_pDocument, &priv->m_nDocumentWidthTwips, &priv->m_nDocumentHeightTwips);
1294 gtk_widget_set_size_request(GTK_WIDGET(pDocView),
1295 twipToPixel(priv->m_nDocumentWidthTwips, priv->m_fZoom),
1296 twipToPixel(priv->m_nDocumentHeightTwips, priv->m_fZoom));
1298 g_signal_emit(pDocView, doc_view_signals[SIZE_CHANGED], 0, nullptr);
1300 break;
1301 case LOK_CALLBACK_SET_PART:
1303 setPart(pDocView, pCallback->m_aPayload);
1305 break;
1306 case LOK_CALLBACK_SEARCH_RESULT_SELECTION:
1308 boost::property_tree::ptree aTree;
1309 std::stringstream aStream(pCallback->m_aPayload);
1310 boost::property_tree::read_json(aStream, aTree);
1311 int nCount = aTree.get_child("searchResultSelection").size();
1312 searchResultCount(pDocView, std::to_string(nCount));
1314 break;
1315 case LOK_CALLBACK_UNO_COMMAND_RESULT:
1317 commandResult(pDocView, pCallback->m_aPayload);
1319 break;
1320 case LOK_CALLBACK_CELL_ADDRESS:
1322 addressChanged(pDocView, pCallback->m_aPayload);
1324 break;
1325 case LOK_CALLBACK_CELL_FORMULA:
1327 formulaChanged(pDocView, pCallback->m_aPayload);
1329 break;
1330 case LOK_CALLBACK_ERROR:
1332 reportError(pDocView, pCallback->m_aPayload);
1334 break;
1335 case LOK_CALLBACK_CONTEXT_MENU:
1337 // TODO: Implement me
1338 break;
1340 case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
1342 std::stringstream aStream(pCallback->m_aPayload);
1343 boost::property_tree::ptree aTree;
1344 boost::property_tree::read_json(aStream, aTree);
1345 int nViewId = aTree.get<int>("viewId");
1346 int nPart = aTree.get<int>("part");
1347 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1348 priv->m_aViewCursors[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1349 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1350 break;
1352 case LOK_CALLBACK_TEXT_VIEW_SELECTION:
1354 std::stringstream aStream(pCallback->m_aPayload);
1355 boost::property_tree::ptree aTree;
1356 boost::property_tree::read_json(aStream, aTree);
1357 int nViewId = aTree.get<int>("viewId");
1358 int nPart = aTree.get<int>("part");
1359 const std::string& rSelection = aTree.get<std::string>("selection");
1360 priv->m_aTextViewSelectionRectangles[nViewId] = ViewRectangles(nPart, payloadToRectangles(pDocView, rSelection.c_str()));
1361 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1362 break;
1364 case LOK_CALLBACK_VIEW_CURSOR_VISIBLE:
1366 std::stringstream aStream(pCallback->m_aPayload);
1367 boost::property_tree::ptree aTree;
1368 boost::property_tree::read_json(aStream, aTree);
1369 int nViewId = aTree.get<int>("viewId");
1370 const std::string& rVisible = aTree.get<std::string>("visible");
1371 priv->m_aViewCursorVisibilities[nViewId] = rVisible == "true";
1372 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1373 break;
1375 break;
1376 case LOK_CALLBACK_CELL_VIEW_CURSOR:
1378 std::stringstream aStream(pCallback->m_aPayload);
1379 boost::property_tree::ptree aTree;
1380 boost::property_tree::read_json(aStream, aTree);
1381 int nViewId = aTree.get<int>("viewId");
1382 int nPart = aTree.get<int>("part");
1383 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1384 if (rRectangle != "EMPTY")
1385 priv->m_aCellViewCursors[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1386 else
1388 auto it = priv->m_aCellViewCursors.find(nViewId);
1389 if (it != priv->m_aCellViewCursors.end())
1390 priv->m_aCellViewCursors.erase(it);
1392 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1393 break;
1395 case LOK_CALLBACK_VIEW_LOCK:
1397 std::stringstream aStream(pCallback->m_aPayload);
1398 boost::property_tree::ptree aTree;
1399 boost::property_tree::read_json(aStream, aTree);
1400 int nViewId = aTree.get<int>("viewId");
1401 int nPart = aTree.get<int>("part");
1402 const std::string& rRectangle = aTree.get<std::string>("rectangle");
1403 if (rRectangle != "EMPTY")
1404 priv->m_aViewLockRectangles[nViewId] = ViewRectangle(nPart, payloadToRectangle(pDocView, rRectangle.c_str()));
1405 else
1407 auto it = priv->m_aViewLockRectangles.find(nViewId);
1408 if (it != priv->m_aViewLockRectangles.end())
1409 priv->m_aViewLockRectangles.erase(it);
1411 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
1412 break;
1414 case LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED:
1416 break;
1418 case LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED:
1420 break;
1422 case LOK_CALLBACK_COMMENT:
1423 g_signal_emit(pCallback->m_pDocView, doc_view_signals[COMMENT], 0, pCallback->m_aPayload.c_str());
1424 break;
1425 default:
1426 g_assert(false);
1427 break;
1429 delete pCallback;
1431 return G_SOURCE_REMOVE;
1434 static void callbackWorker (int nType, const char* pPayload, void* pData)
1436 LOKDocView* pDocView = LOK_DOC_VIEW (pData);
1438 CallbackData* pCallback = new CallbackData(nType, pPayload ? pPayload : "(nil)", pDocView);
1439 LOKDocViewPrivate& priv = getPrivate(pDocView);
1440 std::stringstream ss;
1441 ss << "callbackWorker, view #" << priv->m_nViewId << ": " << callbackTypeToString(nType) << ", '" << (pPayload ? pPayload : "(nil)") << "'";
1442 g_info("%s", ss.str().c_str());
1443 gdk_threads_add_idle(callback, pCallback);
1446 static void
1447 renderHandle(LOKDocView* pDocView,
1448 cairo_t* pCairo,
1449 const GdkRectangle& rCursor,
1450 cairo_surface_t* pHandle,
1451 GdkRectangle& rRectangle)
1453 LOKDocViewPrivate& priv = getPrivate(pDocView);
1454 GdkPoint aCursorBottom;
1455 int nHandleWidth, nHandleHeight;
1456 double fHandleScale;
1458 nHandleWidth = cairo_image_surface_get_width(pHandle);
1459 nHandleHeight = cairo_image_surface_get_height(pHandle);
1460 // We want to scale down the handle, so that its height is the same as the cursor caret.
1461 fHandleScale = twipToPixel(rCursor.height, priv->m_fZoom) / nHandleHeight;
1462 // We want the top center of the handle bitmap to be at the bottom center of the cursor rectangle.
1463 aCursorBottom.x = twipToPixel(rCursor.x, priv->m_fZoom) + twipToPixel(rCursor.width, priv->m_fZoom) / 2 - (nHandleWidth * fHandleScale) / 2;
1464 aCursorBottom.y = twipToPixel(rCursor.y, priv->m_fZoom) + twipToPixel(rCursor.height, priv->m_fZoom);
1466 cairo_save (pCairo);
1467 cairo_translate(pCairo, aCursorBottom.x, aCursorBottom.y);
1468 cairo_scale(pCairo, fHandleScale, fHandleScale);
1469 cairo_set_source_surface(pCairo, pHandle, 0, 0);
1470 cairo_paint(pCairo);
1471 cairo_restore (pCairo);
1473 rRectangle.x = aCursorBottom.x;
1474 rRectangle.y = aCursorBottom.y;
1475 rRectangle.width = nHandleWidth * fHandleScale;
1476 rRectangle.height = nHandleHeight * fHandleScale;
1479 /// Renders handles around an rSelection rectangle on pCairo.
1480 static void
1481 renderGraphicHandle(LOKDocView* pDocView,
1482 cairo_t* pCairo,
1483 const GdkRectangle& rSelection,
1484 const GdkRGBA& rColor)
1486 LOKDocViewPrivate& priv = getPrivate(pDocView);
1487 int nHandleWidth = 9, nHandleHeight = 9;
1488 GdkRectangle aSelection;
1490 aSelection.x = twipToPixel(rSelection.x, priv->m_fZoom);
1491 aSelection.y = twipToPixel(rSelection.y, priv->m_fZoom);
1492 aSelection.width = twipToPixel(rSelection.width, priv->m_fZoom);
1493 aSelection.height = twipToPixel(rSelection.height, priv->m_fZoom);
1495 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
1497 int x = aSelection.x, y = aSelection.y;
1499 switch (i)
1501 case 0: // top-left
1502 break;
1503 case 1: // top-middle
1504 x += aSelection.width / 2;
1505 break;
1506 case 2: // top-right
1507 x += aSelection.width;
1508 break;
1509 case 3: // middle-left
1510 y += aSelection.height / 2;
1511 break;
1512 case 4: // middle-right
1513 x += aSelection.width;
1514 y += aSelection.height / 2;
1515 break;
1516 case 5: // bottom-left
1517 y += aSelection.height;
1518 break;
1519 case 6: // bottom-middle
1520 x += aSelection.width / 2;
1521 y += aSelection.height;
1522 break;
1523 case 7: // bottom-right
1524 x += aSelection.width;
1525 y += aSelection.height;
1526 break;
1529 // Center the handle.
1530 x -= nHandleWidth / 2;
1531 y -= nHandleHeight / 2;
1533 priv->m_aGraphicHandleRects[i].x = x;
1534 priv->m_aGraphicHandleRects[i].y = y;
1535 priv->m_aGraphicHandleRects[i].width = nHandleWidth;
1536 priv->m_aGraphicHandleRects[i].height = nHandleHeight;
1538 cairo_set_source_rgb(pCairo, rColor.red, rColor.green, rColor.blue);
1539 cairo_rectangle(pCairo, x, y, nHandleWidth, nHandleHeight);
1540 cairo_fill(pCairo);
1544 /// Finishes the paint tile operation and returns the result, if any
1545 static gpointer
1546 paintTileFinish(LOKDocView* pDocView, GAsyncResult* res, GError **error)
1548 GTask* task = G_TASK(res);
1550 g_return_val_if_fail(LOK_IS_DOC_VIEW(pDocView), nullptr);
1551 g_return_val_if_fail(g_task_is_valid(res, pDocView), nullptr);
1552 g_return_val_if_fail(error == nullptr || *error == nullptr, nullptr);
1554 return g_task_propagate_pointer(task, error);
1557 /// Callback called in the main UI thread when paintTileInThread in LOK thread has finished
1558 static void
1559 paintTileCallback(GObject* sourceObject, GAsyncResult* res, gpointer userData)
1561 LOKDocView* pDocView = LOK_DOC_VIEW(sourceObject);
1562 LOKDocViewPrivate& priv = getPrivate(pDocView);
1563 LOEvent* pLOEvent = static_cast<LOEvent*>(userData);
1564 std::unique_ptr<TileBuffer>& buffer = priv->m_pTileBuffer;
1565 int index = pLOEvent->m_nPaintTileX * buffer->m_nWidth + pLOEvent->m_nPaintTileY;
1566 GError* error;
1568 error = nullptr;
1569 cairo_surface_t* pSurface = static_cast<cairo_surface_t*>(paintTileFinish(pDocView, res, &error));
1570 if (error != nullptr)
1572 if (error->domain == LOK_TILEBUFFER_ERROR &&
1573 error->code == LOK_TILEBUFFER_CHANGED)
1574 g_info("Skipping paint tile request because corresponding"
1575 "tile buffer has been destroyed");
1576 else
1577 g_warning("Unable to get painted GdkPixbuf: %s", error->message);
1578 g_error_free(error);
1579 return;
1582 buffer->m_mTiles[index].setSurface(pSurface);
1583 buffer->m_mTiles[index].valid = true;
1584 gdk_threads_add_idle(queueDraw, GTK_WIDGET(pDocView));
1586 cairo_surface_destroy(pSurface);
1590 static gboolean
1591 renderDocument(LOKDocView* pDocView, cairo_t* pCairo)
1593 LOKDocViewPrivate& priv = getPrivate(pDocView);
1594 GdkRectangle aVisibleArea;
1595 long nDocumentWidthPixels = twipToPixel(priv->m_nDocumentWidthTwips, priv->m_fZoom);
1596 long nDocumentHeightPixels = twipToPixel(priv->m_nDocumentHeightTwips, priv->m_fZoom);
1597 // Total number of rows / columns in this document.
1598 guint nRows = ceil((double)nDocumentHeightPixels / nTileSizePixels);
1599 guint nColumns = ceil((double)nDocumentWidthPixels / nTileSizePixels);
1601 gdk_cairo_get_clip_rectangle (pCairo, &aVisibleArea);
1602 aVisibleArea.x = pixelToTwip (aVisibleArea.x, priv->m_fZoom);
1603 aVisibleArea.y = pixelToTwip (aVisibleArea.y, priv->m_fZoom);
1604 aVisibleArea.width = pixelToTwip (aVisibleArea.width, priv->m_fZoom);
1605 aVisibleArea.height = pixelToTwip (aVisibleArea.height, priv->m_fZoom);
1607 // Render the tiles.
1608 for (guint nRow = 0; nRow < nRows; ++nRow)
1610 for (guint nColumn = 0; nColumn < nColumns; ++nColumn)
1612 GdkRectangle aTileRectangleTwips, aTileRectanglePixels;
1613 bool bPaint = true;
1615 // Determine size of the tile: the rightmost/bottommost tiles may
1616 // be smaller, and we need the size to decide if we need to repaint.
1617 if (nColumn == nColumns - 1)
1618 aTileRectanglePixels.width = nDocumentWidthPixels - nColumn * nTileSizePixels;
1619 else
1620 aTileRectanglePixels.width = nTileSizePixels;
1621 if (nRow == nRows - 1)
1622 aTileRectanglePixels.height = nDocumentHeightPixels - nRow * nTileSizePixels;
1623 else
1624 aTileRectanglePixels.height = nTileSizePixels;
1626 // Determine size and position of the tile in document coordinates,
1627 // so we can decide if we can skip painting for partial rendering.
1628 aTileRectangleTwips.x = pixelToTwip(nTileSizePixels, priv->m_fZoom) * nColumn;
1629 aTileRectangleTwips.y = pixelToTwip(nTileSizePixels, priv->m_fZoom) * nRow;
1630 aTileRectangleTwips.width = pixelToTwip(aTileRectanglePixels.width, priv->m_fZoom);
1631 aTileRectangleTwips.height = pixelToTwip(aTileRectanglePixels.height, priv->m_fZoom);
1633 if (!gdk_rectangle_intersect(&aVisibleArea, &aTileRectangleTwips, nullptr))
1634 bPaint = false;
1636 if (bPaint)
1638 LOEvent* pLOEvent = new LOEvent(LOK_PAINT_TILE);
1639 pLOEvent->m_nPaintTileX = nRow;
1640 pLOEvent->m_nPaintTileY = nColumn;
1641 pLOEvent->m_fPaintTileZoom = priv->m_fZoom;
1642 pLOEvent->m_pTileBuffer = &*priv->m_pTileBuffer;
1643 GTask* task = g_task_new(pDocView, nullptr, paintTileCallback, pLOEvent);
1644 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
1646 Tile& currentTile = priv->m_pTileBuffer->getTile(nRow, nColumn, task, priv->lokThreadPool);
1647 cairo_surface_t* pSurface = currentTile.getBuffer();
1648 cairo_set_source_surface(pCairo, pSurface,
1649 twipToPixel(aTileRectangleTwips.x, priv->m_fZoom),
1650 twipToPixel(aTileRectangleTwips.y, priv->m_fZoom));
1651 cairo_paint(pCairo);
1652 g_object_unref(task);
1657 return FALSE;
1660 static const GdkRGBA& getDarkColor(int nViewId, LOKDocViewPrivate& priv)
1662 static std::map<int, GdkRGBA> aColorMap;
1663 auto it = aColorMap.find(nViewId);
1664 if (it != aColorMap.end())
1665 return it->second;
1667 if (priv->m_eDocumentType == LOK_DOCTYPE_TEXT)
1669 char* pValues = priv->m_pDocument->pClass->getCommandValues(priv->m_pDocument, ".uno:TrackedChangeAuthors");
1670 std::stringstream aInfo;
1671 aInfo << "lok::Document::getCommandValues('.uno:TrackedChangeAuthors') returned '" << pValues << "'" << std::endl;
1672 g_info("%s", aInfo.str().c_str());
1674 std::stringstream aStream(pValues);
1675 boost::property_tree::ptree aTree;
1676 boost::property_tree::read_json(aStream, aTree);
1677 for (const auto& rValue : aTree.get_child("authors"))
1679 const std::string& rName = rValue.second.get<std::string>("name");
1680 guint32 nColor = rValue.second.get<guint32>("color");
1681 GdkRGBA aColor{((double)((guint8)((nColor)>>16)))/255, ((double)((guint8)(((guint16)(nColor)) >> 8)))/255, ((double)((guint8)(nColor)))/255, 0};
1682 auto itAuthorViews = g_aAuthorViews.find(rName);
1683 if (itAuthorViews != g_aAuthorViews.end())
1684 aColorMap[itAuthorViews->second] = aColor;
1687 else
1689 // Based on tools/colordata.hxx, COL_AUTHOR1_DARK..COL_AUTHOR9_DARK.
1690 static std::vector<GdkRGBA> aColors =
1692 {((double)198)/255, ((double)146)/255, ((double)0)/255, 0},
1693 {((double)6)/255, ((double)70)/255, ((double)162)/255, 0},
1694 {((double)87)/255, ((double)157)/255, ((double)28)/255, 0},
1695 {((double)105)/255, ((double)43)/255, ((double)157)/255, 0},
1696 {((double)197)/255, ((double)0)/255, ((double)11)/255, 0},
1697 {((double)0)/255, ((double)128)/255, ((double)128)/255, 0},
1698 {((double)140)/255, ((double)132)/255, ((double)0)/255, 0},
1699 {((double)43)/255, ((double)85)/255, ((double)107)/255, 0},
1700 {((double)209)/255, ((double)118)/255, ((double)0)/255, 0},
1702 static int nColorCounter = 0;
1703 GdkRGBA aColor = aColors[nColorCounter++ % aColors.size()];
1704 aColorMap[nViewId] = aColor;
1706 assert(aColorMap.find(nViewId) != aColorMap.end());
1707 return aColorMap[nViewId];
1710 static gboolean
1711 renderOverlay(LOKDocView* pDocView, cairo_t* pCairo)
1713 LOKDocViewPrivate& priv = getPrivate(pDocView);
1715 if (priv->m_bEdit && priv->m_bCursorVisible && priv->m_bCursorOverlayVisible && !isEmptyRectangle(priv->m_aVisibleCursor))
1717 if (priv->m_aVisibleCursor.width < 30)
1718 // Set a minimal width if it would be 0.
1719 priv->m_aVisibleCursor.width = 30;
1721 cairo_set_source_rgb(pCairo, 0, 0, 0);
1722 cairo_rectangle(pCairo,
1723 twipToPixel(priv->m_aVisibleCursor.x, priv->m_fZoom),
1724 twipToPixel(priv->m_aVisibleCursor.y, priv->m_fZoom),
1725 twipToPixel(priv->m_aVisibleCursor.width, priv->m_fZoom),
1726 twipToPixel(priv->m_aVisibleCursor.height, priv->m_fZoom));
1727 cairo_fill(pCairo);
1730 // View cursors: they do not blink and are colored.
1731 if (priv->m_bEdit && !priv->m_aViewCursors.empty())
1733 for (auto& rPair : priv->m_aViewCursors)
1735 auto itVisibility = priv->m_aViewCursorVisibilities.find(rPair.first);
1736 if (itVisibility != priv->m_aViewCursorVisibilities.end() && !itVisibility->second)
1737 continue;
1739 // Show view cursors when in Writer or when the part matches.
1740 if (rPair.second.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
1741 continue;
1743 GdkRectangle& rCursor = rPair.second.m_aRectangle;
1744 if (rCursor.width < 30)
1745 // Set a minimal width if it would be 0.
1746 rCursor.width = 30;
1748 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1749 cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
1750 cairo_rectangle(pCairo,
1751 twipToPixel(rCursor.x, priv->m_fZoom),
1752 twipToPixel(rCursor.y, priv->m_fZoom),
1753 twipToPixel(rCursor.width, priv->m_fZoom),
1754 twipToPixel(rCursor.height, priv->m_fZoom));
1755 cairo_fill(pCairo);
1759 if (priv->m_bEdit && priv->m_bCursorVisible && !isEmptyRectangle(priv->m_aVisibleCursor) && priv->m_aTextSelectionRectangles.empty())
1761 // Have a cursor, but no selection: we need the middle handle.
1762 gchar* handleMiddlePath = g_strconcat (priv->m_aLOPath, CURSOR_HANDLE_DIR, "handle_image_middle.png", nullptr);
1763 if (!priv->m_pHandleMiddle)
1765 priv->m_pHandleMiddle = cairo_image_surface_create_from_png(handleMiddlePath);
1766 assert(cairo_surface_status(priv->m_pHandleMiddle) == CAIRO_STATUS_SUCCESS);
1768 g_free (handleMiddlePath);
1769 renderHandle(pDocView, pCairo, priv->m_aVisibleCursor, priv->m_pHandleMiddle, priv->m_aHandleMiddleRect);
1772 if (!priv->m_aTextSelectionRectangles.empty())
1774 for (GdkRectangle& rRectangle : priv->m_aTextSelectionRectangles)
1776 // Blue with 75% transparency.
1777 cairo_set_source_rgba(pCairo, ((double)0x43)/255, ((double)0xac)/255, ((double)0xe8)/255, 0.25);
1778 cairo_rectangle(pCairo,
1779 twipToPixel(rRectangle.x, priv->m_fZoom),
1780 twipToPixel(rRectangle.y, priv->m_fZoom),
1781 twipToPixel(rRectangle.width, priv->m_fZoom),
1782 twipToPixel(rRectangle.height, priv->m_fZoom));
1783 cairo_fill(pCairo);
1786 // Handles
1787 if (!isEmptyRectangle(priv->m_aTextSelectionStart))
1789 // Have a start position: we need a start handle.
1790 gchar* handleStartPath = g_strconcat (priv->m_aLOPath, CURSOR_HANDLE_DIR, "handle_image_start.png", nullptr);
1791 if (!priv->m_pHandleStart)
1793 priv->m_pHandleStart = cairo_image_surface_create_from_png(handleStartPath);
1794 assert(cairo_surface_status(priv->m_pHandleStart) == CAIRO_STATUS_SUCCESS);
1796 renderHandle(pDocView, pCairo, priv->m_aTextSelectionStart, priv->m_pHandleStart, priv->m_aHandleStartRect);
1797 g_free (handleStartPath);
1799 if (!isEmptyRectangle(priv->m_aTextSelectionEnd))
1801 // Have a start position: we need an end handle.
1802 gchar* handleEndPath = g_strconcat (priv->m_aLOPath, CURSOR_HANDLE_DIR, "handle_image_end.png", nullptr);
1803 if (!priv->m_pHandleEnd)
1805 priv->m_pHandleEnd = cairo_image_surface_create_from_png(handleEndPath);
1806 assert(cairo_surface_status(priv->m_pHandleEnd) == CAIRO_STATUS_SUCCESS);
1808 renderHandle(pDocView, pCairo, priv->m_aTextSelectionEnd, priv->m_pHandleEnd, priv->m_aHandleEndRect);
1809 g_free (handleEndPath);
1813 // Selections of other views.
1814 for (auto& rPair : priv->m_aTextViewSelectionRectangles)
1816 if (rPair.second.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
1817 continue;
1819 for (GdkRectangle& rRectangle : rPair.second.m_aRectangles)
1821 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1822 // 75% transparency.
1823 cairo_set_source_rgba(pCairo, rDark.red, rDark.green, rDark.blue, 0.25);
1824 cairo_rectangle(pCairo,
1825 twipToPixel(rRectangle.x, priv->m_fZoom),
1826 twipToPixel(rRectangle.y, priv->m_fZoom),
1827 twipToPixel(rRectangle.width, priv->m_fZoom),
1828 twipToPixel(rRectangle.height, priv->m_fZoom));
1829 cairo_fill(pCairo);
1833 if (!isEmptyRectangle(priv->m_aGraphicSelection))
1835 GdkRGBA aBlack{0, 0, 0, 0};
1836 renderGraphicHandle(pDocView, pCairo, priv->m_aGraphicSelection, aBlack);
1839 // Graphic selections of other views.
1840 for (auto& rPair : priv->m_aGraphicViewSelections)
1842 const ViewRectangle& rRectangle = rPair.second;
1843 if (rRectangle.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT)
1844 continue;
1846 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1847 renderGraphicHandle(pDocView, pCairo, rRectangle.m_aRectangle, rDark);
1850 // Draw the cell cursor.
1851 if (!isEmptyRectangle(priv->m_aCellCursor))
1853 cairo_set_source_rgb(pCairo, 0, 0, 0);
1854 cairo_rectangle(pCairo,
1855 twipToPixel(priv->m_aCellCursor.x, priv->m_fZoom),
1856 twipToPixel(priv->m_aCellCursor.y, priv->m_fZoom),
1857 twipToPixel(priv->m_aCellCursor.width, priv->m_fZoom),
1858 twipToPixel(priv->m_aCellCursor.height, priv->m_fZoom));
1859 cairo_set_line_width(pCairo, 2.0);
1860 cairo_stroke(pCairo);
1863 // Cell view cursors: they are colored.
1864 for (auto& rPair : priv->m_aCellViewCursors)
1866 const ViewRectangle& rCursor = rPair.second;
1867 if (rCursor.m_nPart != priv->m_nPartId)
1868 continue;
1870 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1871 cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
1872 cairo_rectangle(pCairo,
1873 twipToPixel(rCursor.m_aRectangle.x, priv->m_fZoom),
1874 twipToPixel(rCursor.m_aRectangle.y, priv->m_fZoom),
1875 twipToPixel(rCursor.m_aRectangle.width, priv->m_fZoom),
1876 twipToPixel(rCursor.m_aRectangle.height, priv->m_fZoom));
1877 cairo_set_line_width(pCairo, 2.0);
1878 cairo_stroke(pCairo);
1881 // View locks: they are colored.
1882 for (auto& rPair : priv->m_aViewLockRectangles)
1884 const ViewRectangle& rRectangle = rPair.second;
1885 if (rRectangle.m_nPart != priv->m_nPartId)
1886 continue;
1888 // Draw a rectangle.
1889 const GdkRGBA& rDark = getDarkColor(rPair.first, priv);
1890 cairo_set_source_rgb(pCairo, rDark.red, rDark.green, rDark.blue);
1891 cairo_rectangle(pCairo,
1892 twipToPixel(rRectangle.m_aRectangle.x, priv->m_fZoom),
1893 twipToPixel(rRectangle.m_aRectangle.y, priv->m_fZoom),
1894 twipToPixel(rRectangle.m_aRectangle.width, priv->m_fZoom),
1895 twipToPixel(rRectangle.m_aRectangle.height, priv->m_fZoom));
1896 cairo_set_line_width(pCairo, 2.0);
1897 cairo_stroke(pCairo);
1899 // And a lock.
1900 cairo_rectangle(pCairo,
1901 twipToPixel(rRectangle.m_aRectangle.x + rRectangle.m_aRectangle.width, priv->m_fZoom) - 25,
1902 twipToPixel(rRectangle.m_aRectangle.y + rRectangle.m_aRectangle.height, priv->m_fZoom) - 15,
1904 10);
1905 cairo_fill(pCairo);
1906 cairo_arc(pCairo,
1907 twipToPixel(rRectangle.m_aRectangle.x + rRectangle.m_aRectangle.width, priv->m_fZoom) - 15,
1908 twipToPixel(rRectangle.m_aRectangle.y + rRectangle.m_aRectangle.height, priv->m_fZoom) - 15,
1910 180.0 * (M_PI/180.0),
1911 360.0 * (M_PI/180.0));
1912 cairo_stroke(pCairo);
1915 return FALSE;
1918 static gboolean
1919 lok_doc_view_signal_button(GtkWidget* pWidget, GdkEventButton* pEvent)
1921 LOKDocView* pDocView = LOK_DOC_VIEW (pWidget);
1922 LOKDocViewPrivate& priv = getPrivate(pDocView);
1923 GError* error = nullptr;
1925 g_info("LOKDocView_Impl::signalButton: %d, %d (in twips: %d, %d)",
1926 (int)pEvent->x, (int)pEvent->y,
1927 (int)pixelToTwip(pEvent->x, priv->m_fZoom),
1928 (int)pixelToTwip(pEvent->y, priv->m_fZoom));
1929 gtk_widget_grab_focus(GTK_WIDGET(pDocView));
1931 switch (pEvent->type)
1933 case GDK_BUTTON_PRESS:
1935 GdkRectangle aClick;
1936 aClick.x = pEvent->x;
1937 aClick.y = pEvent->y;
1938 aClick.width = 1;
1939 aClick.height = 1;
1941 if (handleTextSelectionOnButtonPress(aClick, pDocView))
1942 return FALSE;
1943 if (handleGraphicSelectionOnButtonPress(aClick, pDocView))
1944 return FALSE;
1946 int nCount = 1;
1947 if ((pEvent->time - priv->m_nLastButtonPressTime) < 250)
1948 nCount++;
1949 priv->m_nLastButtonPressTime = pEvent->time;
1950 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
1951 LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
1952 pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEBUTTONDOWN;
1953 pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
1954 pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
1955 pLOEvent->m_nPostMouseEventCount = nCount;
1956 switch (pEvent->button)
1958 case 1:
1959 pLOEvent->m_nPostMouseEventButton = MOUSE_LEFT;
1960 break;
1961 case 2:
1962 pLOEvent->m_nPostMouseEventButton = MOUSE_MIDDLE;
1963 break;
1964 case 3:
1965 pLOEvent->m_nPostMouseEventButton = MOUSE_RIGHT;
1966 break;
1968 pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
1969 priv->m_nLastButtonPressed = pLOEvent->m_nPostMouseEventButton;
1970 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
1972 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
1973 if (error != nullptr)
1975 g_warning("Unable to call LOK_POST_MOUSE_EVENT: %s", error->message);
1976 g_clear_error(&error);
1978 g_object_unref(task);
1979 break;
1981 case GDK_BUTTON_RELEASE:
1983 if (handleTextSelectionOnButtonRelease(pDocView))
1984 return FALSE;
1985 if (handleGraphicSelectionOnButtonRelease(pDocView, pEvent))
1986 return FALSE;
1988 int nCount = 1;
1989 if ((pEvent->time - priv->m_nLastButtonReleaseTime) < 250)
1990 nCount++;
1991 priv->m_nLastButtonReleaseTime = pEvent->time;
1992 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
1993 LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
1994 pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEBUTTONUP;
1995 pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
1996 pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
1997 pLOEvent->m_nPostMouseEventCount = nCount;
1998 switch (pEvent->button)
2000 case 1:
2001 pLOEvent->m_nPostMouseEventButton = MOUSE_LEFT;
2002 break;
2003 case 2:
2004 pLOEvent->m_nPostMouseEventButton = MOUSE_MIDDLE;
2005 break;
2006 case 3:
2007 pLOEvent->m_nPostMouseEventButton = MOUSE_RIGHT;
2008 break;
2010 pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
2011 priv->m_nLastButtonPressed = pLOEvent->m_nPostMouseEventButton;
2012 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2014 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2015 if (error != nullptr)
2017 g_warning("Unable to call LOK_POST_MOUSE_EVENT: %s", error->message);
2018 g_clear_error(&error);
2020 g_object_unref(task);
2021 break;
2023 default:
2024 break;
2026 return FALSE;
2029 static void
2030 getDragPoint(GdkRectangle* pHandle,
2031 GdkEventMotion* pEvent,
2032 GdkPoint* pPoint)
2034 GdkPoint aCursor, aHandle;
2036 // Center of the cursor rectangle: we know that it's above the handle.
2037 aCursor.x = pHandle->x + pHandle->width / 2;
2038 aCursor.y = pHandle->y - pHandle->height / 2;
2039 // Center of the handle rectangle.
2040 aHandle.x = pHandle->x + pHandle->width / 2;
2041 aHandle.y = pHandle->y + pHandle->height / 2;
2042 // Our target is the original cursor position + the dragged offset.
2043 pPoint->x = aCursor.x + (pEvent->x - aHandle.x);
2044 pPoint->y = aCursor.y + (pEvent->y - aHandle.y);
2047 static gboolean
2048 lok_doc_view_signal_motion (GtkWidget* pWidget, GdkEventMotion* pEvent)
2050 LOKDocView* pDocView = LOK_DOC_VIEW (pWidget);
2051 LOKDocViewPrivate& priv = getPrivate(pDocView);
2052 GdkPoint aPoint;
2053 GError* error = nullptr;
2055 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2056 std::stringstream ss;
2057 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2058 g_info("%s", ss.str().c_str());
2059 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2060 if (priv->m_bInDragMiddleHandle)
2062 g_info("lcl_signalMotion: dragging the middle handle");
2063 getDragPoint(&priv->m_aHandleMiddleRect, pEvent, &aPoint);
2064 priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_RESET, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
2065 return FALSE;
2067 if (priv->m_bInDragStartHandle)
2069 g_info("lcl_signalMotion: dragging the start handle");
2070 getDragPoint(&priv->m_aHandleStartRect, pEvent, &aPoint);
2071 priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_START, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
2072 return FALSE;
2074 if (priv->m_bInDragEndHandle)
2076 g_info("lcl_signalMotion: dragging the end handle");
2077 getDragPoint(&priv->m_aHandleEndRect, pEvent, &aPoint);
2078 priv->m_pDocument->pClass->setTextSelection(priv->m_pDocument, LOK_SETTEXTSELECTION_END, pixelToTwip(aPoint.x, priv->m_fZoom), pixelToTwip(aPoint.y, priv->m_fZoom));
2079 return FALSE;
2081 aGuard.unlock();
2082 for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
2084 if (priv->m_bInDragGraphicHandles[i])
2086 g_info("lcl_signalMotion: dragging the graphic handle #%d", i);
2087 return FALSE;
2090 if (priv->m_bInDragGraphicSelection)
2092 g_info("lcl_signalMotion: dragging the graphic selection");
2093 return FALSE;
2096 GdkRectangle aMotionInTwipsInTwips;
2097 aMotionInTwipsInTwips.x = pixelToTwip(pEvent->x, priv->m_fZoom);
2098 aMotionInTwipsInTwips.y = pixelToTwip(pEvent->y, priv->m_fZoom);
2099 aMotionInTwipsInTwips.width = 1;
2100 aMotionInTwipsInTwips.height = 1;
2101 if (gdk_rectangle_intersect(&aMotionInTwipsInTwips, &priv->m_aGraphicSelection, nullptr))
2103 g_info("lcl_signalMotion: start of drag graphic selection");
2104 priv->m_bInDragGraphicSelection = true;
2106 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2107 LOEvent* pLOEvent = new LOEvent(LOK_SET_GRAPHIC_SELECTION);
2108 pLOEvent->m_nSetGraphicSelectionType = LOK_SETGRAPHICSELECTION_START;
2109 pLOEvent->m_nSetGraphicSelectionX = pixelToTwip(pEvent->x, priv->m_fZoom);
2110 pLOEvent->m_nSetGraphicSelectionY = pixelToTwip(pEvent->y, priv->m_fZoom);
2111 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2113 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2114 if (error != nullptr)
2116 g_warning("Unable to call LOK_SET_GRAPHIC_SELECTION: %s", error->message);
2117 g_clear_error(&error);
2119 g_object_unref(task);
2121 return FALSE;
2124 // Otherwise a mouse move, as on the desktop.
2126 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
2127 LOEvent* pLOEvent = new LOEvent(LOK_POST_MOUSE_EVENT);
2128 pLOEvent->m_nPostMouseEventType = LOK_MOUSEEVENT_MOUSEMOVE;
2129 pLOEvent->m_nPostMouseEventX = pixelToTwip(pEvent->x, priv->m_fZoom);
2130 pLOEvent->m_nPostMouseEventY = pixelToTwip(pEvent->y, priv->m_fZoom);
2131 pLOEvent->m_nPostMouseEventCount = 1;
2132 pLOEvent->m_nPostMouseEventButton = priv->m_nLastButtonPressed;
2133 pLOEvent->m_nPostMouseEventModifier = priv->m_nKeyModifier;
2135 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
2137 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
2138 if (error != nullptr)
2140 g_warning("Unable to call LOK_MOUSEEVENT_MOUSEMOVE: %s", error->message);
2141 g_clear_error(&error);
2143 g_object_unref(task);
2145 return FALSE;
2148 static void
2149 setGraphicSelectionInThread(gpointer data)
2151 GTask* task = G_TASK(data);
2152 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2153 LOKDocViewPrivate& priv = getPrivate(pDocView);
2154 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2156 std::lock_guard<std::mutex> aGuard(g_aLOKMutex);
2157 std::stringstream ss;
2158 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2159 g_info("%s", ss.str().c_str());
2160 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2161 ss.str(std::string());
2162 ss << "lok::Document::setGraphicSelection(" << pLOEvent->m_nSetGraphicSelectionType;
2163 ss << ", " << pLOEvent->m_nSetGraphicSelectionX;
2164 ss << ", " << pLOEvent->m_nSetGraphicSelectionY << ")";
2165 g_info("%s", ss.str().c_str());
2166 priv->m_pDocument->pClass->setGraphicSelection(priv->m_pDocument,
2167 pLOEvent->m_nSetGraphicSelectionType,
2168 pLOEvent->m_nSetGraphicSelectionX,
2169 pLOEvent->m_nSetGraphicSelectionY);
2172 static void
2173 setClientZoomInThread(gpointer data)
2175 GTask* task = G_TASK(data);
2176 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2177 LOKDocViewPrivate& priv = getPrivate(pDocView);
2178 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2180 std::lock_guard<std::mutex> aGuard(g_aLOKMutex);
2181 priv->m_pDocument->pClass->setClientZoom(priv->m_pDocument,
2182 pLOEvent->m_nTilePixelWidth,
2183 pLOEvent->m_nTilePixelHeight,
2184 pLOEvent->m_nTileTwipWidth,
2185 pLOEvent->m_nTileTwipHeight);
2188 static void
2189 postMouseEventInThread(gpointer data)
2191 GTask* task = G_TASK(data);
2192 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2193 LOKDocViewPrivate& priv = getPrivate(pDocView);
2194 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2196 std::lock_guard<std::mutex> aGuard(g_aLOKMutex);
2197 std::stringstream ss;
2198 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2199 g_info("%s", ss.str().c_str());
2200 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2201 ss.str(std::string());
2202 ss << "lok::Document::postMouseEvent(" << pLOEvent->m_nPostMouseEventType;
2203 ss << ", " << pLOEvent->m_nPostMouseEventX;
2204 ss << ", " << pLOEvent->m_nPostMouseEventY;
2205 ss << ", " << pLOEvent->m_nPostMouseEventCount;
2206 ss << ", " << pLOEvent->m_nPostMouseEventButton;
2207 ss << ", " << pLOEvent->m_nPostMouseEventModifier << ")";
2208 g_info("%s", ss.str().c_str());
2209 priv->m_pDocument->pClass->postMouseEvent(priv->m_pDocument,
2210 pLOEvent->m_nPostMouseEventType,
2211 pLOEvent->m_nPostMouseEventX,
2212 pLOEvent->m_nPostMouseEventY,
2213 pLOEvent->m_nPostMouseEventCount,
2214 pLOEvent->m_nPostMouseEventButton,
2215 pLOEvent->m_nPostMouseEventModifier);
2218 static void
2219 openDocumentInThread (gpointer data)
2221 GTask* task = G_TASK(data);
2222 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2223 LOKDocViewPrivate& priv = getPrivate(pDocView);
2225 std::lock_guard<std::mutex> aGuard(g_aLOKMutex);
2226 if ( priv->m_pDocument )
2228 priv->m_pDocument->pClass->destroy( priv->m_pDocument );
2229 priv->m_pDocument = nullptr;
2232 priv->m_pOffice->pClass->registerCallback(priv->m_pOffice, globalCallbackWorker, pDocView);
2233 priv->m_pDocument = priv->m_pOffice->pClass->documentLoad( priv->m_pOffice, priv->m_aDocPath );
2234 priv->m_eDocumentType = static_cast<LibreOfficeKitDocumentType>(priv->m_pDocument->pClass->getDocumentType(priv->m_pDocument));
2235 if ( !priv->m_pDocument )
2237 char *pError = priv->m_pOffice->pClass->getError( priv->m_pOffice );
2238 g_task_return_new_error(task, g_quark_from_static_string ("LOK error"), 0, "%s", pError);
2240 else
2242 gdk_threads_add_idle(postDocumentLoad, pDocView);
2243 g_task_return_boolean (task, true);
2247 static void
2248 setPartInThread(gpointer data)
2250 GTask* task = G_TASK(data);
2251 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2252 LOKDocViewPrivate& priv = getPrivate(pDocView);
2253 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2254 int nPart = pLOEvent->m_nPart;
2256 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2257 std::stringstream ss;
2258 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2259 g_info("%s", ss.str().c_str());
2260 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2261 priv->m_pDocument->pClass->setPart( priv->m_pDocument, nPart );
2262 aGuard.unlock();
2264 lok_doc_view_reset_view(pDocView);
2267 static void
2268 setPartmodeInThread(gpointer data)
2270 GTask* task = G_TASK(data);
2271 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2272 LOKDocViewPrivate& priv = getPrivate(pDocView);
2273 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2274 int nPartMode = pLOEvent->m_nPartMode;
2276 std::lock_guard<std::mutex> aGuard(g_aLOKMutex);
2277 std::stringstream ss;
2278 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2279 g_info("%s", ss.str().c_str());
2280 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2281 priv->m_pDocument->pClass->setPartMode( priv->m_pDocument, nPartMode );
2284 static void
2285 setEditInThread(gpointer data)
2287 GTask* task = G_TASK(data);
2288 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2289 LOKDocViewPrivate& priv = getPrivate(pDocView);
2290 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2291 gboolean bWasEdit = priv->m_bEdit;
2292 gboolean bEdit = pLOEvent->m_bEdit;
2294 if (!priv->m_bEdit && bEdit)
2295 g_info("lok_doc_view_set_edit: entering edit mode");
2296 else if (priv->m_bEdit && !bEdit)
2298 g_info("lok_doc_view_set_edit: leaving edit mode");
2299 std::lock_guard<std::mutex> aGuard(g_aLOKMutex);
2300 std::stringstream ss;
2301 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2302 g_info("%s", ss.str().c_str());
2303 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2304 priv->m_pDocument->pClass->resetSelection(priv->m_pDocument);
2306 priv->m_bEdit = bEdit;
2307 g_signal_emit(pDocView, doc_view_signals[EDIT_CHANGED], 0, bWasEdit);
2308 gdk_threads_add_idle(queueDraw, GTK_WIDGET(pDocView));
2311 static void
2312 postCommandInThread (gpointer data)
2314 GTask* task = G_TASK(data);
2315 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2316 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2317 LOKDocViewPrivate& priv = getPrivate(pDocView);
2319 std::lock_guard<std::mutex> aGuard(g_aLOKMutex);
2320 std::stringstream ss;
2321 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2322 g_info("%s", ss.str().c_str());
2323 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2324 ss.str(std::string());
2325 ss << "lok::Document::postUnoCommand(" << pLOEvent->m_pCommand << ", " << pLOEvent->m_pArguments << ")";
2326 g_info("%s", ss.str().c_str());
2327 priv->m_pDocument->pClass->postUnoCommand(priv->m_pDocument, pLOEvent->m_pCommand, pLOEvent->m_pArguments, pLOEvent->m_bNotifyWhenFinished);
2330 static void
2331 paintTileInThread (gpointer data)
2333 GTask* task = G_TASK(data);
2334 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2335 LOKDocViewPrivate& priv = getPrivate(pDocView);
2336 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2338 // check if "source" tile buffer is different from "current" tile buffer
2339 if (pLOEvent->m_pTileBuffer != &*priv->m_pTileBuffer)
2341 pLOEvent->m_pTileBuffer = nullptr;
2342 g_task_return_new_error(task,
2343 LOK_TILEBUFFER_ERROR,
2344 LOK_TILEBUFFER_CHANGED,
2345 "TileBuffer has changed");
2346 return;
2348 std::unique_ptr<TileBuffer>& buffer = priv->m_pTileBuffer;
2349 int index = pLOEvent->m_nPaintTileX * buffer->m_nWidth + pLOEvent->m_nPaintTileY;
2350 if (buffer->m_mTiles.find(index) != buffer->m_mTiles.end() &&
2351 buffer->m_mTiles[index].valid)
2352 return;
2354 cairo_surface_t *pSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nTileSizePixels, nTileSizePixels);
2355 if (cairo_surface_status(pSurface) != CAIRO_STATUS_SUCCESS)
2357 cairo_surface_destroy(pSurface);
2358 g_task_return_new_error(task,
2359 LOK_TILEBUFFER_ERROR,
2360 LOK_TILEBUFFER_MEMORY,
2361 "Error allocating Surface");
2362 return;
2365 unsigned char* pBuffer = cairo_image_surface_get_data(pSurface);
2366 GdkRectangle aTileRectangle;
2367 aTileRectangle.x = pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom) * pLOEvent->m_nPaintTileY;
2368 aTileRectangle.y = pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom) * pLOEvent->m_nPaintTileX;
2370 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2371 std::stringstream ss;
2372 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2373 g_info("%s", ss.str().c_str());
2374 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2375 ss.str(std::string());
2376 GTimer* aTimer = g_timer_new();
2377 gulong nElapsedMs;
2378 ss << "lok::Document::paintTile(" << static_cast<void*>(pBuffer) << ", "
2379 << nTileSizePixels << ", " << nTileSizePixels << ", "
2380 << aTileRectangle.x << ", " << aTileRectangle.y << ", "
2381 << pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom) << ", "
2382 << pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom) << ")";
2384 priv->m_pDocument->pClass->paintTile(priv->m_pDocument,
2385 pBuffer,
2386 nTileSizePixels, nTileSizePixels,
2387 aTileRectangle.x, aTileRectangle.y,
2388 pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom),
2389 pixelToTwip(nTileSizePixels, pLOEvent->m_fPaintTileZoom));
2390 aGuard.unlock();
2392 g_timer_elapsed(aTimer, &nElapsedMs);
2393 ss << " rendered in " << (nElapsedMs / 1000.) << " milliseconds";
2394 g_info("%s", ss.str().c_str());
2395 g_timer_destroy(aTimer);
2397 cairo_surface_mark_dirty(pSurface);
2399 // Its likely that while the tilebuffer has changed, one of the paint tile
2400 // requests has passed the previous check at start of this function, and has
2401 // rendered the tile already. We want to stop such rendered tiles from being
2402 // stored in new tile buffer.
2403 if (pLOEvent->m_pTileBuffer != &*priv->m_pTileBuffer)
2405 pLOEvent->m_pTileBuffer = nullptr;
2406 g_task_return_new_error(task,
2407 LOK_TILEBUFFER_ERROR,
2408 LOK_TILEBUFFER_CHANGED,
2409 "TileBuffer has changed");
2410 return;
2413 g_task_return_pointer(task, pSurface, reinterpret_cast<GDestroyNotify>(cairo_surface_destroy));
2417 static void
2418 lokThreadFunc(gpointer data, gpointer /*user_data*/)
2420 GTask* task = G_TASK(data);
2421 LOEvent* pLOEvent = static_cast<LOEvent*>(g_task_get_task_data(task));
2422 LOKDocView* pDocView = LOK_DOC_VIEW(g_task_get_source_object(task));
2423 LOKDocViewPrivate& priv = getPrivate(pDocView);
2425 switch (pLOEvent->m_nType)
2427 case LOK_LOAD_DOC:
2428 openDocumentInThread(task);
2429 break;
2430 case LOK_POST_COMMAND:
2431 postCommandInThread(task);
2432 break;
2433 case LOK_SET_EDIT:
2434 setEditInThread(task);
2435 break;
2436 case LOK_SET_PART:
2437 setPartInThread(task);
2438 break;
2439 case LOK_SET_PARTMODE:
2440 setPartmodeInThread(task);
2441 break;
2442 case LOK_POST_KEY:
2443 // view-only/editable mode already checked during signal key signal emission
2444 postKeyEventInThread(task);
2445 break;
2446 case LOK_PAINT_TILE:
2447 paintTileInThread(task);
2448 break;
2449 case LOK_POST_MOUSE_EVENT:
2450 postMouseEventInThread(task);
2451 break;
2452 case LOK_SET_GRAPHIC_SELECTION:
2453 if (priv->m_bEdit)
2454 setGraphicSelectionInThread(task);
2455 else
2456 g_info ("LOK_SET_GRAPHIC_SELECTION: skipping graphical operation in view-only mode");
2457 break;
2458 case LOK_SET_CLIENT_ZOOM:
2459 setClientZoomInThread(task);
2460 break;
2463 g_object_unref(task);
2466 static void lok_doc_view_init (LOKDocView* pDocView)
2468 LOKDocViewPrivate& priv = getPrivate(pDocView);
2469 priv.m_pImpl = new LOKDocViewPrivateImpl();
2471 gtk_widget_add_events(GTK_WIDGET(pDocView),
2472 GDK_BUTTON_PRESS_MASK
2473 |GDK_BUTTON_RELEASE_MASK
2474 |GDK_BUTTON_MOTION_MASK
2475 |GDK_KEY_PRESS_MASK
2476 |GDK_KEY_RELEASE_MASK);
2478 priv->lokThreadPool = g_thread_pool_new(lokThreadFunc,
2479 nullptr,
2481 FALSE,
2482 nullptr);
2485 static void lok_doc_view_set_property (GObject* object, guint propId, const GValue *value, GParamSpec *pspec)
2487 LOKDocView* pDocView = LOK_DOC_VIEW (object);
2488 LOKDocViewPrivate& priv = getPrivate(pDocView);
2489 gboolean bDocPasswordEnabled = priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD;
2490 gboolean bDocPasswordToModifyEnabled = priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY;
2491 gboolean bTiledAnnotationsEnabled = !(priv->m_nLOKFeatures & LOK_FEATURE_NO_TILED_ANNOTATIONS);
2493 switch (propId)
2495 case PROP_LO_PATH:
2496 priv->m_aLOPath = g_value_dup_string (value);
2497 break;
2498 case PROP_LO_POINTER:
2499 priv->m_pOffice = static_cast<LibreOfficeKit*>(g_value_get_pointer(value));
2500 break;
2501 case PROP_USER_PROFILE_URL:
2502 priv->m_pUserProfileURL = g_value_dup_string(value);
2503 break;
2504 case PROP_DOC_PATH:
2505 priv->m_aDocPath = g_value_dup_string (value);
2506 break;
2507 case PROP_DOC_POINTER:
2508 priv->m_pDocument = static_cast<LibreOfficeKitDocument*>(g_value_get_pointer(value));
2509 priv->m_eDocumentType = static_cast<LibreOfficeKitDocumentType>(priv->m_pDocument->pClass->getDocumentType(priv->m_pDocument));
2510 break;
2511 case PROP_EDITABLE:
2512 lok_doc_view_set_edit (pDocView, g_value_get_boolean (value));
2513 break;
2514 case PROP_ZOOM:
2515 lok_doc_view_set_zoom (pDocView, g_value_get_float (value));
2516 break;
2517 case PROP_DOC_WIDTH:
2518 priv->m_nDocumentWidthTwips = g_value_get_long (value);
2519 break;
2520 case PROP_DOC_HEIGHT:
2521 priv->m_nDocumentHeightTwips = g_value_get_long (value);
2522 break;
2523 case PROP_DOC_PASSWORD:
2524 if (g_value_get_boolean (value) != bDocPasswordEnabled)
2526 priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_DOCUMENT_PASSWORD;
2527 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2529 break;
2530 case PROP_DOC_PASSWORD_TO_MODIFY:
2531 if ( g_value_get_boolean (value) != bDocPasswordToModifyEnabled)
2533 priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY;
2534 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2536 break;
2537 case PROP_TILED_ANNOTATIONS:
2538 if ( g_value_get_boolean (value) != bTiledAnnotationsEnabled)
2540 priv->m_nLOKFeatures = priv->m_nLOKFeatures ^ LOK_FEATURE_NO_TILED_ANNOTATIONS;
2541 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2543 break;
2544 default:
2545 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec);
2549 static void lok_doc_view_get_property (GObject* object, guint propId, GValue *value, GParamSpec *pspec)
2551 LOKDocView* pDocView = LOK_DOC_VIEW (object);
2552 LOKDocViewPrivate& priv = getPrivate(pDocView);
2554 switch (propId)
2556 case PROP_LO_PATH:
2557 g_value_set_string (value, priv->m_aLOPath);
2558 break;
2559 case PROP_LO_POINTER:
2560 g_value_set_pointer(value, priv->m_pOffice);
2561 break;
2562 case PROP_USER_PROFILE_URL:
2563 g_value_set_string(value, priv->m_pUserProfileURL);
2564 break;
2565 case PROP_DOC_PATH:
2566 g_value_set_string (value, priv->m_aDocPath);
2567 break;
2568 case PROP_DOC_POINTER:
2569 g_value_set_pointer(value, priv->m_pDocument);
2570 break;
2571 case PROP_EDITABLE:
2572 g_value_set_boolean (value, priv->m_bEdit);
2573 break;
2574 case PROP_LOAD_PROGRESS:
2575 g_value_set_double (value, priv->m_nLoadProgress);
2576 break;
2577 case PROP_ZOOM:
2578 g_value_set_float (value, priv->m_fZoom);
2579 break;
2580 case PROP_IS_LOADING:
2581 g_value_set_boolean (value, priv->m_bIsLoading);
2582 break;
2583 case PROP_DOC_WIDTH:
2584 g_value_set_long (value, priv->m_nDocumentWidthTwips);
2585 break;
2586 case PROP_DOC_HEIGHT:
2587 g_value_set_long (value, priv->m_nDocumentHeightTwips);
2588 break;
2589 case PROP_CAN_ZOOM_IN:
2590 g_value_set_boolean (value, priv->m_bCanZoomIn);
2591 break;
2592 case PROP_CAN_ZOOM_OUT:
2593 g_value_set_boolean (value, priv->m_bCanZoomOut);
2594 break;
2595 case PROP_DOC_PASSWORD:
2596 g_value_set_boolean (value, priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD);
2597 break;
2598 case PROP_DOC_PASSWORD_TO_MODIFY:
2599 g_value_set_boolean (value, priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY);
2600 break;
2601 case PROP_TILED_ANNOTATIONS:
2602 g_value_set_boolean (value, !(priv->m_nLOKFeatures & LOK_FEATURE_NO_TILED_ANNOTATIONS));
2603 break;
2604 default:
2605 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec);
2609 static gboolean lok_doc_view_draw (GtkWidget* pWidget, cairo_t* pCairo)
2611 LOKDocView *pDocView = LOK_DOC_VIEW (pWidget);
2613 renderDocument (pDocView, pCairo);
2614 renderOverlay (pDocView, pCairo);
2616 return FALSE;
2619 //rhbz#1444437 finalize may not occur immediately when this widget is destroyed
2620 //it may happen during GC of javascript, e.g. in gnome-documents but "destroy"
2621 //will be called promptly, so close documents in destroy, not finalize
2622 static void lok_doc_view_destroy (GtkWidget* widget)
2624 LOKDocView* pDocView = LOK_DOC_VIEW (widget);
2625 LOKDocViewPrivate& priv = getPrivate(pDocView);
2627 // Ignore notifications sent to this view on shutdown.
2628 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
2629 std::stringstream ss;
2630 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
2631 g_info("%s", ss.str().c_str());
2632 if (priv->m_pDocument)
2634 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
2635 priv->m_pDocument->pClass->registerCallback(priv->m_pDocument, nullptr, nullptr);
2638 if (priv->lokThreadPool)
2640 g_thread_pool_free(priv->lokThreadPool, true, true);
2641 priv->lokThreadPool = nullptr;
2644 aGuard.unlock();
2646 if (priv->m_pDocument)
2648 if (priv->m_pDocument->pClass->getViewsCount(priv->m_pDocument) > 1)
2650 priv->m_pDocument->pClass->destroyView(priv->m_pDocument, priv->m_nViewId);
2652 else
2654 if (priv->m_pDocument)
2656 priv->m_pDocument->pClass->destroy (priv->m_pDocument);
2657 priv->m_pDocument = nullptr;
2659 if (priv->m_pOffice)
2661 priv->m_pOffice->pClass->destroy (priv->m_pOffice);
2662 priv->m_pOffice = nullptr;
2667 GTK_WIDGET_CLASS (lok_doc_view_parent_class)->destroy (widget);
2670 static void lok_doc_view_finalize (GObject* object)
2672 LOKDocView* pDocView = LOK_DOC_VIEW (object);
2673 LOKDocViewPrivate& priv = getPrivate(pDocView);
2675 delete priv.m_pImpl;
2676 priv.m_pImpl = nullptr;
2678 G_OBJECT_CLASS (lok_doc_view_parent_class)->finalize (object);
2681 static gboolean lok_doc_view_initable_init (GInitable *initable, GCancellable* /*cancellable*/, GError **error)
2683 LOKDocView *pDocView = LOK_DOC_VIEW (initable);
2684 LOKDocViewPrivate& priv = getPrivate(pDocView);
2686 if (priv->m_pOffice != nullptr)
2687 return TRUE;
2689 priv->m_pOffice = lok_init_2(priv->m_aLOPath, priv->m_pUserProfileURL);
2691 if (priv->m_pOffice == nullptr)
2693 g_set_error (error,
2694 g_quark_from_static_string ("LOK initialization error"), 0,
2695 "Failed to get LibreOfficeKit context. Make sure path (%s) is correct",
2696 priv->m_aLOPath);
2697 return FALSE;
2699 priv->m_nLOKFeatures |= LOK_FEATURE_PART_IN_INVALIDATION_CALLBACK;
2700 priv->m_pOffice->pClass->setOptionalFeatures(priv->m_pOffice, priv->m_nLOKFeatures);
2702 return TRUE;
2705 static void lok_doc_view_initable_iface_init (GInitableIface *iface)
2707 iface->init = lok_doc_view_initable_init;
2710 static void lok_doc_view_class_init (LOKDocViewClass* pClass)
2712 GObjectClass *pGObjectClass = G_OBJECT_CLASS(pClass);
2713 GtkWidgetClass *pWidgetClass = GTK_WIDGET_CLASS(pClass);
2715 pGObjectClass->get_property = lok_doc_view_get_property;
2716 pGObjectClass->set_property = lok_doc_view_set_property;
2717 pGObjectClass->finalize = lok_doc_view_finalize;
2719 pWidgetClass->draw = lok_doc_view_draw;
2720 pWidgetClass->button_press_event = lok_doc_view_signal_button;
2721 pWidgetClass->button_release_event = lok_doc_view_signal_button;
2722 pWidgetClass->key_press_event = signalKey;
2723 pWidgetClass->key_release_event = signalKey;
2724 pWidgetClass->motion_notify_event = lok_doc_view_signal_motion;
2725 pWidgetClass->destroy = lok_doc_view_destroy;
2728 * LOKDocView:lopath:
2730 * The absolute path of the LibreOffice install.
2732 properties[PROP_LO_PATH] =
2733 g_param_spec_string("lopath",
2734 "LO Path",
2735 "LibreOffice Install Path",
2736 nullptr,
2737 static_cast<GParamFlags>(G_PARAM_READWRITE |
2738 G_PARAM_CONSTRUCT_ONLY |
2739 G_PARAM_STATIC_STRINGS));
2742 * LOKDocView:lopointer:
2744 * A LibreOfficeKit* in case lok_init() is already called
2745 * previously.
2747 properties[PROP_LO_POINTER] =
2748 g_param_spec_pointer("lopointer",
2749 "LO Pointer",
2750 "A LibreOfficeKit* from lok_init()",
2751 static_cast<GParamFlags>(G_PARAM_READWRITE |
2752 G_PARAM_CONSTRUCT_ONLY |
2753 G_PARAM_STATIC_STRINGS));
2756 * LOKDocView:userprofileurl:
2758 * The absolute path of the LibreOffice user profile.
2760 properties[PROP_USER_PROFILE_URL] =
2761 g_param_spec_string("userprofileurl",
2762 "User profile path",
2763 "LibreOffice user profile path",
2764 nullptr,
2765 static_cast<GParamFlags>(G_PARAM_READWRITE |
2766 G_PARAM_CONSTRUCT_ONLY |
2767 G_PARAM_STATIC_STRINGS));
2770 * LOKDocView:docpath:
2772 * The path of the document that is currently being viewed.
2774 properties[PROP_DOC_PATH] =
2775 g_param_spec_string("docpath",
2776 "Document Path",
2777 "The URI of the document to open",
2778 nullptr,
2779 static_cast<GParamFlags>(G_PARAM_READWRITE |
2780 G_PARAM_STATIC_STRINGS));
2783 * LOKDocView:docpointer:
2785 * A LibreOfficeKitDocument* in case documentLoad() is already called
2786 * previously.
2788 properties[PROP_DOC_POINTER] =
2789 g_param_spec_pointer("docpointer",
2790 "Document Pointer",
2791 "A LibreOfficeKitDocument* from documentLoad()",
2792 static_cast<GParamFlags>(G_PARAM_READWRITE |
2793 G_PARAM_STATIC_STRINGS));
2796 * LOKDocView:editable:
2798 * Whether the document loaded inside of #LOKDocView is editable or not.
2800 properties[PROP_EDITABLE] =
2801 g_param_spec_boolean("editable",
2802 "Editable",
2803 "Whether the content is in edit mode or not",
2804 FALSE,
2805 static_cast<GParamFlags>(G_PARAM_READWRITE |
2806 G_PARAM_STATIC_STRINGS));
2809 * LOKDocView:load-progress:
2811 * The percent completion of the current loading operation of the
2812 * document. This can be used for progress bars. Note that this is not a
2813 * very accurate progress indicator, and its value might reset it couple of
2814 * times to 0 and start again. You should not rely on its numbers.
2816 properties[PROP_LOAD_PROGRESS] =
2817 g_param_spec_double("load-progress",
2818 "Estimated Load Progress",
2819 "Shows the progress of the document load operation",
2820 0.0, 1.0, 0.0,
2821 static_cast<GParamFlags>(G_PARAM_READABLE |
2822 G_PARAM_STATIC_STRINGS));
2825 * LOKDocView:zoom-level:
2827 * The current zoom level of the document loaded inside #LOKDocView. The
2828 * default value is 1.0.
2830 properties[PROP_ZOOM] =
2831 g_param_spec_float("zoom-level",
2832 "Zoom Level",
2833 "The current zoom level of the content",
2834 0, 5.0, 1.0,
2835 static_cast<GParamFlags>(G_PARAM_READWRITE |
2836 G_PARAM_STATIC_STRINGS));
2839 * LOKDocView:is-loading:
2841 * Whether the requested document is being loaded or not. %TRUE if it is
2842 * being loaded, otherwise %FALSE.
2844 properties[PROP_IS_LOADING] =
2845 g_param_spec_boolean("is-loading",
2846 "Is Loading",
2847 "Whether the view is loading a document",
2848 FALSE,
2849 static_cast<GParamFlags>(G_PARAM_READABLE |
2850 G_PARAM_STATIC_STRINGS));
2853 * LOKDocView:doc-width:
2855 * The width of the currently loaded document in #LOKDocView in twips.
2857 properties[PROP_DOC_WIDTH] =
2858 g_param_spec_long("doc-width",
2859 "Document Width",
2860 "Width of the document in twips",
2861 0, G_MAXLONG, 0,
2862 static_cast<GParamFlags>(G_PARAM_READWRITE |
2863 G_PARAM_STATIC_STRINGS));
2866 * LOKDocView:doc-height:
2868 * The height of the currently loaded document in #LOKDocView in twips.
2870 properties[PROP_DOC_HEIGHT] =
2871 g_param_spec_long("doc-height",
2872 "Document Height",
2873 "Height of the document in twips",
2874 0, G_MAXLONG, 0,
2875 static_cast<GParamFlags>(G_PARAM_READWRITE |
2876 G_PARAM_STATIC_STRINGS));
2879 * LOKDocView:can-zoom-in:
2881 * It tells whether the view can further be zoomed in or not.
2883 properties[PROP_CAN_ZOOM_IN] =
2884 g_param_spec_boolean("can-zoom-in",
2885 "Can Zoom In",
2886 "Whether the view can be zoomed in further",
2887 TRUE,
2888 static_cast<GParamFlags>(G_PARAM_READABLE
2889 | G_PARAM_STATIC_STRINGS));
2892 * LOKDocView:can-zoom-out:
2894 * It tells whether the view can further be zoomed out or not.
2896 properties[PROP_CAN_ZOOM_OUT] =
2897 g_param_spec_boolean("can-zoom-out",
2898 "Can Zoom Out",
2899 "Whether the view can be zoomed out further",
2900 TRUE,
2901 static_cast<GParamFlags>(G_PARAM_READABLE
2902 | G_PARAM_STATIC_STRINGS));
2905 * LOKDocView:doc-password:
2907 * Set it to true if client supports providing password for viewing
2908 * password protected documents
2910 properties[PROP_DOC_PASSWORD] =
2911 g_param_spec_boolean("doc-password",
2912 "Document password capability",
2913 "Whether client supports providing document passwords",
2914 FALSE,
2915 static_cast<GParamFlags>(G_PARAM_READWRITE
2916 | G_PARAM_STATIC_STRINGS));
2919 * LOKDocView:doc-password-to-modify:
2921 * Set it to true if client supports providing password for edit-protected documents
2923 properties[PROP_DOC_PASSWORD_TO_MODIFY] =
2924 g_param_spec_boolean("doc-password-to-modify",
2925 "Edit document password capability",
2926 "Whether the client supports providing passwords to edit documents",
2927 FALSE,
2928 static_cast<GParamFlags>(G_PARAM_READWRITE
2929 | G_PARAM_STATIC_STRINGS));
2932 * LOKDocView:tiled-annotations-rendering:
2934 * Set it to false if client does not want LO to render comments in tiles and
2935 * instead interested in using comments API to access comments
2937 properties[PROP_TILED_ANNOTATIONS] =
2938 g_param_spec_boolean("tiled-annotations",
2939 "Render comments in tiles",
2940 "Whether the client wants in tile comment rendering",
2941 TRUE,
2942 static_cast<GParamFlags>(G_PARAM_READWRITE
2943 | G_PARAM_STATIC_STRINGS));
2945 g_object_class_install_properties(pGObjectClass, PROP_LAST, properties);
2948 * LOKDocView::load-changed:
2949 * @pDocView: the #LOKDocView on which the signal is emitted
2950 * @fLoadProgress: the new progress value
2952 doc_view_signals[LOAD_CHANGED] =
2953 g_signal_new("load-changed",
2954 G_TYPE_FROM_CLASS (pGObjectClass),
2955 G_SIGNAL_RUN_FIRST,
2957 nullptr, nullptr,
2958 g_cclosure_marshal_VOID__DOUBLE,
2959 G_TYPE_NONE, 1,
2960 G_TYPE_DOUBLE);
2963 * LOKDocView::edit-changed:
2964 * @pDocView: the #LOKDocView on which the signal is emitted
2965 * @bEdit: the new edit value of the view
2967 doc_view_signals[EDIT_CHANGED] =
2968 g_signal_new("edit-changed",
2969 G_TYPE_FROM_CLASS (pGObjectClass),
2970 G_SIGNAL_RUN_FIRST,
2972 nullptr, nullptr,
2973 g_cclosure_marshal_VOID__BOOLEAN,
2974 G_TYPE_NONE, 1,
2975 G_TYPE_BOOLEAN);
2978 * LOKDocView::command-changed:
2979 * @pDocView: the #LOKDocView on which the signal is emitted
2980 * @aCommand: the command that was changed
2982 doc_view_signals[COMMAND_CHANGED] =
2983 g_signal_new("command-changed",
2984 G_TYPE_FROM_CLASS(pGObjectClass),
2985 G_SIGNAL_RUN_FIRST,
2987 nullptr, nullptr,
2988 g_cclosure_marshal_VOID__STRING,
2989 G_TYPE_NONE, 1,
2990 G_TYPE_STRING);
2993 * LOKDocView::search-not-found:
2994 * @pDocView: the #LOKDocView on which the signal is emitted
2995 * @aCommand: the string for which the search was not found.
2997 doc_view_signals[SEARCH_NOT_FOUND] =
2998 g_signal_new("search-not-found",
2999 G_TYPE_FROM_CLASS(pGObjectClass),
3000 G_SIGNAL_RUN_FIRST,
3002 nullptr, nullptr,
3003 g_cclosure_marshal_VOID__STRING,
3004 G_TYPE_NONE, 1,
3005 G_TYPE_STRING);
3008 * LOKDocView::part-changed:
3009 * @pDocView: the #LOKDocView on which the signal is emitted
3010 * @aCommand: the part number which the view changed to
3012 doc_view_signals[PART_CHANGED] =
3013 g_signal_new("part-changed",
3014 G_TYPE_FROM_CLASS(pGObjectClass),
3015 G_SIGNAL_RUN_FIRST,
3017 nullptr, nullptr,
3018 g_cclosure_marshal_VOID__INT,
3019 G_TYPE_NONE, 1,
3020 G_TYPE_INT);
3023 * LOKDocView::size-changed:
3024 * @pDocView: the #LOKDocView on which the signal is emitted
3025 * @aCommand: NULL, we just notify that want to notify the UI elements that are interested.
3027 doc_view_signals[SIZE_CHANGED] =
3028 g_signal_new("size-changed",
3029 G_TYPE_FROM_CLASS(pGObjectClass),
3030 G_SIGNAL_RUN_FIRST,
3032 nullptr, nullptr,
3033 g_cclosure_marshal_VOID__VOID,
3034 G_TYPE_NONE, 1,
3035 G_TYPE_INT);
3038 * LOKDocView::hyperlinked-clicked:
3039 * @pDocView: the #LOKDocView on which the signal is emitted
3040 * @aHyperlink: the URI which the application should handle
3042 doc_view_signals[HYPERLINK_CLICKED] =
3043 g_signal_new("hyperlink-clicked",
3044 G_TYPE_FROM_CLASS(pGObjectClass),
3045 G_SIGNAL_RUN_FIRST,
3047 nullptr, nullptr,
3048 g_cclosure_marshal_VOID__STRING,
3049 G_TYPE_NONE, 1,
3050 G_TYPE_STRING);
3053 * LOKDocView::cursor-changed:
3054 * @pDocView: the #LOKDocView on which the signal is emitted
3055 * @nX: The new cursor position (X coordinate) in pixels
3056 * @nY: The new cursor position (Y coordinate) in pixels
3057 * @nWidth: The width of new cursor
3058 * @nHeight: The height of new cursor
3060 doc_view_signals[CURSOR_CHANGED] =
3061 g_signal_new("cursor-changed",
3062 G_TYPE_FROM_CLASS(pGObjectClass),
3063 G_SIGNAL_RUN_FIRST,
3065 nullptr, nullptr,
3066 g_cclosure_marshal_generic,
3067 G_TYPE_NONE, 4,
3068 G_TYPE_INT, G_TYPE_INT,
3069 G_TYPE_INT, G_TYPE_INT);
3072 * LOKDocView::search-result-count:
3073 * @pDocView: the #LOKDocView on which the signal is emitted
3074 * @aCommand: number of matches.
3076 doc_view_signals[SEARCH_RESULT_COUNT] =
3077 g_signal_new("search-result-count",
3078 G_TYPE_FROM_CLASS(pGObjectClass),
3079 G_SIGNAL_RUN_FIRST,
3081 nullptr, nullptr,
3082 g_cclosure_marshal_VOID__STRING,
3083 G_TYPE_NONE, 1,
3084 G_TYPE_STRING);
3087 * LOKDocView::command-result:
3088 * @pDocView: the #LOKDocView on which the signal is emitted
3089 * @aCommand: JSON containing the info about the command that finished,
3090 * and its success status.
3092 doc_view_signals[COMMAND_RESULT] =
3093 g_signal_new("command-result",
3094 G_TYPE_FROM_CLASS(pGObjectClass),
3095 G_SIGNAL_RUN_FIRST,
3097 nullptr, nullptr,
3098 g_cclosure_marshal_VOID__STRING,
3099 G_TYPE_NONE, 1,
3100 G_TYPE_STRING);
3103 * LOKDocView::address-changed:
3104 * @pDocView: the #LOKDocView on which the signal is emitted
3105 * @aCommand: formula text content
3107 doc_view_signals[ADDRESS_CHANGED] =
3108 g_signal_new("address-changed",
3109 G_TYPE_FROM_CLASS(pGObjectClass),
3110 G_SIGNAL_RUN_FIRST,
3112 nullptr, nullptr,
3113 g_cclosure_marshal_VOID__STRING,
3114 G_TYPE_NONE, 1,
3115 G_TYPE_STRING);
3118 * LOKDocView::formula-changed:
3119 * @pDocView: the #LOKDocView on which the signal is emitted
3120 * @aCommand: formula text content
3122 doc_view_signals[FORMULA_CHANGED] =
3123 g_signal_new("formula-changed",
3124 G_TYPE_FROM_CLASS(pGObjectClass),
3125 G_SIGNAL_RUN_FIRST,
3127 nullptr, nullptr,
3128 g_cclosure_marshal_VOID__STRING,
3129 G_TYPE_NONE, 1,
3130 G_TYPE_STRING);
3133 * LOKDocView::text-selection:
3134 * @pDocView: the #LOKDocView on which the signal is emitted
3135 * @bIsTextSelected: whether text selected is non-null
3137 doc_view_signals[TEXT_SELECTION] =
3138 g_signal_new("text-selection",
3139 G_TYPE_FROM_CLASS(pGObjectClass),
3140 G_SIGNAL_RUN_FIRST,
3142 nullptr, nullptr,
3143 g_cclosure_marshal_VOID__BOOLEAN,
3144 G_TYPE_NONE, 1,
3145 G_TYPE_BOOLEAN);
3148 * LOKDocView::password-required:
3149 * @pDocView: the #LOKDocView on which the signal is emitted
3150 * @pUrl: URL of the document for which password is required
3151 * @bModify: whether password id required to modify the document
3152 * This is true when password is required to edit the document,
3153 * while it can still be viewed without password. In such cases, provide a NULL
3154 * password for read-only access to the document.
3155 * If false, password is required for opening the document, and document
3156 * cannot be opened without providing a valid password.
3158 * Password must be provided by calling lok_doc_view_set_document_password
3159 * function with pUrl as provided by the callback.
3161 * Upon entering a invalid password, another `password-required` signal is
3162 * emitted.
3163 * Upon entering a valid password, document starts to load.
3164 * Upon entering a NULL password: if bModify is %TRUE, document starts to
3165 * open in view-only mode, else loading of document is aborted.
3167 doc_view_signals[PASSWORD_REQUIRED] =
3168 g_signal_new("password-required",
3169 G_TYPE_FROM_CLASS(pGObjectClass),
3170 G_SIGNAL_RUN_FIRST,
3172 nullptr, nullptr,
3173 g_cclosure_marshal_generic,
3174 G_TYPE_NONE, 2,
3175 G_TYPE_STRING,
3176 G_TYPE_BOOLEAN);
3179 * LOKDocView::comment:
3180 * @pDocView: the #LOKDocView on which the signal is emitted
3181 * @pComment: the JSON string containing comment notification
3182 * The has following structure containing the information telling whether
3183 * the comment has been added, deleted or modified.
3184 * The example:
3186 * "comment": {
3187 * "action": "Add",
3188 * "id": "11",
3189 * "parent": "4",
3190 * "author": "Unknown Author",
3191 * "text": "This is a comment",
3192 * "dateTime": "2016-08-18T13:13:00",
3193 * "anchorPos": "4529, 3906",
3194 * "textRange": "1418, 3906, 3111, 919"
3197 * 'action' can be 'Add', 'Remove' or 'Modify' depending on whether
3198 * comment has been added, removed or modified.
3199 * 'parent' is a non-zero comment id if this comment is a reply comment,
3200 * otherwise its a root comment.
3202 doc_view_signals[COMMENT] =
3203 g_signal_new("comment",
3204 G_TYPE_FROM_CLASS(pGObjectClass),
3205 G_SIGNAL_RUN_FIRST,
3207 nullptr, nullptr,
3208 g_cclosure_marshal_generic,
3209 G_TYPE_NONE, 1,
3210 G_TYPE_STRING);
3213 SAL_DLLPUBLIC_EXPORT GtkWidget*
3214 lok_doc_view_new (const gchar* pPath, GCancellable *cancellable, GError **error)
3216 return GTK_WIDGET (g_initable_new (LOK_TYPE_DOC_VIEW, cancellable, error,
3217 "lopath", pPath == nullptr ? LOK_PATH : pPath,
3218 "halign", GTK_ALIGN_CENTER,
3219 "valign", GTK_ALIGN_CENTER,
3220 nullptr));
3223 SAL_DLLPUBLIC_EXPORT GtkWidget*
3224 lok_doc_view_new_from_user_profile (const gchar* pPath, const gchar* pUserProfile, GCancellable *cancellable, GError **error)
3226 return GTK_WIDGET(g_initable_new(LOK_TYPE_DOC_VIEW, cancellable, error,
3227 "lopath", pPath == nullptr ? LOK_PATH : pPath,
3228 "userprofileurl", pUserProfile,
3229 "halign", GTK_ALIGN_CENTER,
3230 "valign", GTK_ALIGN_CENTER,
3231 nullptr));
3234 SAL_DLLPUBLIC_EXPORT GtkWidget* lok_doc_view_new_from_widget(LOKDocView* pOldLOKDocView,
3235 const gchar* pRenderingArguments)
3237 LOKDocViewPrivate& pOldPriv = getPrivate(pOldLOKDocView);
3238 GtkWidget* pNewDocView = GTK_WIDGET(g_initable_new(LOK_TYPE_DOC_VIEW, /*cancellable=*/nullptr, /*error=*/nullptr,
3239 "lopath", pOldPriv->m_aLOPath,
3240 "userprofileurl", pOldPriv->m_pUserProfileURL,
3241 "lopointer", pOldPriv->m_pOffice,
3242 "docpointer", pOldPriv->m_pDocument,
3243 "halign", GTK_ALIGN_CENTER,
3244 "valign", GTK_ALIGN_CENTER,
3245 nullptr));
3247 // No documentLoad(), just a createView().
3248 LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(pNewDocView));
3249 LOKDocViewPrivate& pNewPriv = getPrivate(LOK_DOC_VIEW(pNewDocView));
3250 // Store the view id only later in postDocumentLoad(), as
3251 // initializeForRendering() changes the id in Impress.
3252 pDocument->pClass->createView(pDocument);
3253 pNewPriv->m_aRenderingArguments = pRenderingArguments;
3255 postDocumentLoad(pNewDocView);
3256 return pNewDocView;
3259 SAL_DLLPUBLIC_EXPORT gboolean
3260 lok_doc_view_open_document_finish (LOKDocView* pDocView, GAsyncResult* res, GError** error)
3262 GTask* task = G_TASK(res);
3264 g_return_val_if_fail(g_task_is_valid(res, pDocView), false);
3265 g_return_val_if_fail(g_task_get_source_tag(task) == lok_doc_view_open_document, false);
3266 g_return_val_if_fail(error == nullptr || *error == nullptr, false);
3268 return g_task_propagate_boolean(task, error);
3271 SAL_DLLPUBLIC_EXPORT void
3272 lok_doc_view_open_document (LOKDocView* pDocView,
3273 const gchar* pPath,
3274 const gchar* pRenderingArguments,
3275 GCancellable* cancellable,
3276 GAsyncReadyCallback callback,
3277 gpointer userdata)
3279 GTask* task = g_task_new(pDocView, cancellable, callback, userdata);
3280 LOKDocViewPrivate& priv = getPrivate(pDocView);
3281 GError* error = nullptr;
3283 LOEvent* pLOEvent = new LOEvent(LOK_LOAD_DOC);
3284 pLOEvent->m_pPath = pPath;
3286 priv->m_aDocPath = pPath;
3287 if (pRenderingArguments)
3288 priv->m_aRenderingArguments = pRenderingArguments;
3289 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3290 g_task_set_source_tag(task, reinterpret_cast<gpointer>(lok_doc_view_open_document));
3292 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3293 if (error != nullptr)
3295 g_warning("Unable to call LOK_LOAD_DOC: %s", error->message);
3296 g_clear_error(&error);
3298 g_object_unref(task);
3301 SAL_DLLPUBLIC_EXPORT LibreOfficeKitDocument*
3302 lok_doc_view_get_document (LOKDocView* pDocView)
3304 LOKDocViewPrivate& priv = getPrivate(pDocView);
3305 return priv->m_pDocument;
3308 SAL_DLLPUBLIC_EXPORT void
3309 lok_doc_view_set_visible_area (LOKDocView* pDocView, GdkRectangle* pVisibleArea)
3311 if (!pVisibleArea)
3312 return;
3314 LOKDocViewPrivate& priv = getPrivate(pDocView);
3315 priv->m_aVisibleArea = *pVisibleArea;
3316 priv->m_bVisibleAreaSet = true;
3319 namespace {
3320 // This used to be rtl::math::approxEqual() but since that isn't inline anymore
3321 // in rtl/math.hxx and was moved into libuno_sal as rtl_math_approxEqual() to
3322 // cater for representable integer cases and we don't want to link against
3323 // libuno_sal, we'll have to have an own implementation. The special large
3324 // integer cases seems not be needed here.
3325 inline bool lok_approxEqual(double a, double b)
3327 static const double e48 = 1.0 / (16777216.0 * 16777216.0);
3328 if (a == b)
3329 return true;
3330 if (a == 0.0 || b == 0.0)
3331 return false;
3332 const double d = fabs(a - b);
3333 return (d < fabs(a) * e48 && d < fabs(b) * e48);
3337 SAL_DLLPUBLIC_EXPORT void
3338 lok_doc_view_set_zoom (LOKDocView* pDocView, float fZoom)
3340 LOKDocViewPrivate& priv = getPrivate(pDocView);
3341 GError* error = nullptr;
3343 if (!priv->m_pDocument)
3344 return;
3346 // Clamp the input value in [MIN_ZOOM, MAX_ZOOM]
3347 fZoom = fZoom < MIN_ZOOM ? MIN_ZOOM : fZoom;
3348 fZoom = fZoom > MAX_ZOOM ? MAX_ZOOM : fZoom;
3350 if (lok_approxEqual(fZoom, priv->m_fZoom))
3351 return;
3353 priv->m_fZoom = fZoom;
3354 long nDocumentWidthPixels = twipToPixel(priv->m_nDocumentWidthTwips, fZoom);
3355 long nDocumentHeightPixels = twipToPixel(priv->m_nDocumentHeightTwips, fZoom);
3356 // Total number of columns in this document.
3357 guint nColumns = ceil((double)nDocumentWidthPixels / nTileSizePixels);
3359 priv->m_pTileBuffer = std::unique_ptr<TileBuffer>(new TileBuffer(nColumns));
3360 gtk_widget_set_size_request(GTK_WIDGET(pDocView),
3361 nDocumentWidthPixels,
3362 nDocumentHeightPixels);
3364 g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_ZOOM]);
3366 // set properties to indicate if view can be further zoomed in/out
3367 bool bCanZoomIn = priv->m_fZoom < MAX_ZOOM;
3368 bool bCanZoomOut = priv->m_fZoom > MIN_ZOOM;
3369 if (bCanZoomIn != bool(priv->m_bCanZoomIn))
3371 priv->m_bCanZoomIn = bCanZoomIn;
3372 g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_CAN_ZOOM_IN]);
3374 if (bCanZoomOut != bool(priv->m_bCanZoomOut))
3376 priv->m_bCanZoomOut = bCanZoomOut;
3377 g_object_notify_by_pspec(G_OBJECT(pDocView), properties[PROP_CAN_ZOOM_OUT]);
3380 // Update the client's view size
3381 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3382 LOEvent* pLOEvent = new LOEvent(LOK_SET_CLIENT_ZOOM);
3383 pLOEvent->m_nTilePixelWidth = nTileSizePixels;
3384 pLOEvent->m_nTilePixelHeight = nTileSizePixels;
3385 pLOEvent->m_nTileTwipWidth = pixelToTwip(nTileSizePixels, fZoom);
3386 pLOEvent->m_nTileTwipHeight = pixelToTwip(nTileSizePixels, fZoom);
3387 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3389 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3390 if (error != nullptr)
3392 g_warning("Unable to call LOK_SET_CLIENT_ZOOM: %s", error->message);
3393 g_clear_error(&error);
3395 g_object_unref(task);
3397 priv->m_nTileSizeTwips = pixelToTwip(nTileSizePixels, priv->m_fZoom);
3400 SAL_DLLPUBLIC_EXPORT gfloat
3401 lok_doc_view_get_zoom (LOKDocView* pDocView)
3403 LOKDocViewPrivate& priv = getPrivate(pDocView);
3404 return priv->m_fZoom;
3407 SAL_DLLPUBLIC_EXPORT gint
3408 lok_doc_view_get_parts (LOKDocView* pDocView)
3410 LOKDocViewPrivate& priv = getPrivate(pDocView);
3411 if (!priv->m_pDocument)
3412 return -1;
3414 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
3415 std::stringstream ss;
3416 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
3417 g_info("%s", ss.str().c_str());
3418 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
3419 return priv->m_pDocument->pClass->getParts( priv->m_pDocument );
3422 SAL_DLLPUBLIC_EXPORT gint
3423 lok_doc_view_get_part (LOKDocView* pDocView)
3425 LOKDocViewPrivate& priv = getPrivate(pDocView);
3426 if (!priv->m_pDocument)
3427 return -1;
3429 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
3430 std::stringstream ss;
3431 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
3432 g_info("%s", ss.str().c_str());
3433 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
3434 return priv->m_pDocument->pClass->getPart( priv->m_pDocument );
3437 SAL_DLLPUBLIC_EXPORT void
3438 lok_doc_view_set_part (LOKDocView* pDocView, int nPart)
3440 LOKDocViewPrivate& priv = getPrivate(pDocView);
3441 if (!priv->m_pDocument)
3442 return;
3444 if (nPart < 0 || nPart >= priv->m_nParts)
3446 g_warning("Invalid part request : %d", nPart);
3447 return;
3450 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3451 LOEvent* pLOEvent = new LOEvent(LOK_SET_PART);
3452 GError* error = nullptr;
3454 pLOEvent->m_nPart = nPart;
3455 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3457 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3458 if (error != nullptr)
3460 g_warning("Unable to call LOK_SET_PART: %s", error->message);
3461 g_clear_error(&error);
3463 g_object_unref(task);
3464 priv->m_nPartId = nPart;
3467 SAL_DLLPUBLIC_EXPORT gchar*
3468 lok_doc_view_get_part_name (LOKDocView* pDocView, int nPart)
3470 LOKDocViewPrivate& priv = getPrivate(pDocView);
3471 if (!priv->m_pDocument)
3472 return nullptr;
3474 std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
3475 std::stringstream ss;
3476 ss << "lok::Document::setView(" << priv->m_nViewId << ")";
3477 g_info("%s", ss.str().c_str());
3478 priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
3479 return priv->m_pDocument->pClass->getPartName( priv->m_pDocument, nPart );
3482 SAL_DLLPUBLIC_EXPORT void
3483 lok_doc_view_set_partmode(LOKDocView* pDocView,
3484 int nPartMode)
3486 LOKDocViewPrivate& priv = getPrivate(pDocView);
3487 if (!priv->m_pDocument)
3488 return;
3490 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3491 LOEvent* pLOEvent = new LOEvent(LOK_SET_PARTMODE);
3492 GError* error = nullptr;
3494 pLOEvent->m_nPartMode = nPartMode;
3495 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3497 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3498 if (error != nullptr)
3500 g_warning("Unable to call LOK_SET_PARTMODE: %s", error->message);
3501 g_clear_error(&error);
3503 g_object_unref(task);
3506 SAL_DLLPUBLIC_EXPORT void
3507 lok_doc_view_reset_view(LOKDocView* pDocView)
3509 LOKDocViewPrivate& priv = getPrivate(pDocView);
3511 if (priv->m_pTileBuffer != nullptr)
3512 priv->m_pTileBuffer->resetAllTiles();
3513 priv->m_nLoadProgress = 0.0;
3515 memset(&priv->m_aVisibleCursor, 0, sizeof(priv->m_aVisibleCursor));
3516 priv->m_bCursorOverlayVisible = false;
3517 priv->m_bCursorVisible = false;
3519 priv->m_nLastButtonPressTime = 0;
3520 priv->m_nLastButtonReleaseTime = 0;
3521 priv->m_aTextSelectionRectangles.clear();
3523 memset(&priv->m_aTextSelectionStart, 0, sizeof(priv->m_aTextSelectionStart));
3524 memset(&priv->m_aTextSelectionEnd, 0, sizeof(priv->m_aTextSelectionEnd));
3525 memset(&priv->m_aGraphicSelection, 0, sizeof(priv->m_aGraphicSelection));
3526 priv->m_bInDragGraphicSelection = false;
3527 memset(&priv->m_aCellCursor, 0, sizeof(priv->m_aCellCursor));
3529 cairo_surface_destroy(priv->m_pHandleStart);
3530 priv->m_pHandleStart = nullptr;
3531 memset(&priv->m_aHandleStartRect, 0, sizeof(priv->m_aHandleStartRect));
3532 priv->m_bInDragStartHandle = false;
3534 cairo_surface_destroy(priv->m_pHandleMiddle);
3535 priv->m_pHandleMiddle = nullptr;
3536 memset(&priv->m_aHandleMiddleRect, 0, sizeof(priv->m_aHandleMiddleRect));
3537 priv->m_bInDragMiddleHandle = false;
3539 cairo_surface_destroy(priv->m_pHandleEnd);
3540 priv->m_pHandleEnd = nullptr;
3541 memset(&priv->m_aHandleEndRect, 0, sizeof(priv->m_aHandleEndRect));
3542 priv->m_bInDragEndHandle = false;
3544 memset(&priv->m_aGraphicHandleRects, 0, sizeof(priv->m_aGraphicHandleRects));
3545 memset(&priv->m_bInDragGraphicHandles, 0, sizeof(priv->m_bInDragGraphicHandles));
3547 gtk_widget_queue_draw(GTK_WIDGET(pDocView));
3550 SAL_DLLPUBLIC_EXPORT void
3551 lok_doc_view_set_edit(LOKDocView* pDocView,
3552 gboolean bEdit)
3554 LOKDocViewPrivate& priv = getPrivate(pDocView);
3555 if (!priv->m_pDocument)
3556 return;
3558 GTask* task = g_task_new(pDocView, nullptr, nullptr, nullptr);
3559 LOEvent* pLOEvent = new LOEvent(LOK_SET_EDIT);
3560 GError* error = nullptr;
3562 pLOEvent->m_bEdit = bEdit;
3563 g_task_set_task_data(task, pLOEvent, LOEvent::destroy);
3565 g_thread_pool_push(priv->lokThreadPool, g_object_ref(task), &error);
3566 if (error != nullptr)
3568 g_warning("Unable to call LOK_SET_EDIT: %s", error->message);
3569 g_clear_error(&error);
3571 g_object_unref(task);
3574 SAL_DLLPUBLIC_EXPORT gboolean
3575 lok_doc_view_get_edit (LOKDocView* pDocView)
3577 LOKDocViewPrivate& priv = getPrivate(pDocView);
3578 return priv->m_bEdit;
3581 SAL_DLLPUBLIC_EXPORT void
3582 lok_doc_view_post_command (LOKDocView* pDocView,
3583 const gchar* pCommand,
3584 const gchar* pArguments,
3585 gboolean bNotifyWhenFinished)
3587 LOKDocViewPrivate& priv = getPrivate(pDocView);
3588 if (!priv->m_pDocument)
3589 return;
3591 if (priv->m_bEdit)
3592 LOKPostCommand(pDocView, pCommand, pArguments, bNotifyWhenFinished);
3593 else
3594 g_info ("LOK_POST_COMMAND: ignoring commands in view-only mode");
3597 SAL_DLLPUBLIC_EXPORT void
3598 lok_doc_view_find_prev (LOKDocView* pDocView,
3599 const gchar* pText,
3600 gboolean bHighlightAll)
3602 doSearch(pDocView, pText, true, bHighlightAll);
3605 SAL_DLLPUBLIC_EXPORT void
3606 lok_doc_view_find_next (LOKDocView* pDocView,
3607 const gchar* pText,
3608 gboolean bHighlightAll)
3610 doSearch(pDocView, pText, false, bHighlightAll);
3613 SAL_DLLPUBLIC_EXPORT void
3614 lok_doc_view_highlight_all (LOKDocView* pDocView,
3615 const gchar* pText)
3617 doSearch(pDocView, pText, false, true);
3620 SAL_DLLPUBLIC_EXPORT gchar*
3621 lok_doc_view_copy_selection (LOKDocView* pDocView,
3622 const gchar* pMimeType,
3623 gchar** pUsedMimeType)
3625 LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(pDocView);
3626 if (!pDocument)
3627 return nullptr;
3629 std::stringstream ss;
3630 ss << "lok::Document::getTextSelection('" << pMimeType << "')";
3631 g_info("%s", ss.str().c_str());
3632 return pDocument->pClass->getTextSelection(pDocument, pMimeType, pUsedMimeType);
3635 SAL_DLLPUBLIC_EXPORT gboolean
3636 lok_doc_view_paste (LOKDocView* pDocView,
3637 const gchar* pMimeType,
3638 const gchar* pData,
3639 gsize nSize)
3641 LOKDocViewPrivate& priv = getPrivate(pDocView);
3642 LibreOfficeKitDocument* pDocument = priv->m_pDocument;
3643 gboolean ret = 0;
3645 if (!pDocument)
3646 return false;
3648 if (!priv->m_bEdit)
3650 g_info ("ignoring paste in view-only mode");
3651 return ret;
3654 if (pData)
3656 std::stringstream ss;
3657 ss << "lok::Document::paste('" << pMimeType << "', '" << std::string(pData, nSize) << ", "<<nSize<<"')";
3658 g_info("%s", ss.str().c_str());
3659 ret = pDocument->pClass->paste(pDocument, pMimeType, pData, nSize);
3662 return ret;
3665 SAL_DLLPUBLIC_EXPORT void
3666 lok_doc_view_set_document_password (LOKDocView* pDocView,
3667 const gchar* pURL,
3668 const gchar* pPassword)
3670 LOKDocViewPrivate& priv = getPrivate(pDocView);
3672 priv->m_pOffice->pClass->setDocumentPassword(priv->m_pOffice, pURL, pPassword);
3675 SAL_DLLPUBLIC_EXPORT gchar*
3676 lok_doc_view_get_version_info (LOKDocView* pDocView)
3678 LOKDocViewPrivate& priv = getPrivate(pDocView);
3680 return priv->m_pOffice->pClass->getVersionInfo(priv->m_pOffice);
3684 SAL_DLLPUBLIC_EXPORT gfloat
3685 lok_doc_view_pixel_to_twip (LOKDocView* pDocView, float fInput)
3687 LOKDocViewPrivate& priv = getPrivate(pDocView);
3688 return pixelToTwip(fInput, priv->m_fZoom);
3691 SAL_DLLPUBLIC_EXPORT gfloat
3692 lok_doc_view_twip_to_pixel (LOKDocView* pDocView, float fInput)
3694 LOKDocViewPrivate& priv = getPrivate(pDocView);
3695 return twipToPixel(fInput, priv->m_fZoom);
3698 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */