Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
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 : */
9 :
10 : #include <sal/types.h>
11 : #include <math.h>
12 : #include <string.h>
13 : #include <vector>
14 : #include <string>
15 :
16 : #include <gdk/gdkkeysyms.h>
17 :
18 : #include <com/sun/star/awt/Key.hpp>
19 : #define LOK_USE_UNSTABLE_API
20 : #include <LibreOfficeKit/LibreOfficeKit.h>
21 : #include <LibreOfficeKit/LibreOfficeKitEnums.h>
22 : #include <LibreOfficeKit/LibreOfficeKitGtk.h>
23 : #include <rsc/rsc-vcl-shared-types.hxx>
24 :
25 : #include "tilebuffer.hxx"
26 :
27 : #if !GLIB_CHECK_VERSION(2,32,0)
28 : #define G_SOURCE_REMOVE FALSE
29 : #define G_SOURCE_CONTINUE TRUE
30 : #endif
31 : #if !GLIB_CHECK_VERSION(2,40,0)
32 : #define g_info(...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__)
33 : #endif
34 :
35 : // Cursor bitmaps from the Android app.
36 : #define CURSOR_HANDLE_DIR "android/source/res/drawable/"
37 : // Number of handles around a graphic selection.
38 : #define GRAPHIC_HANDLE_COUNT 8
39 :
40 : /// Holds data used by LOKDocView only.
41 : struct LOKDocView_Impl
42 : {
43 : LOKDocView* m_pDocView;
44 : GtkWidget *m_pDrawingArea;
45 : TileBuffer m_aTileBuffer;
46 :
47 : float m_fZoom;
48 :
49 : LibreOfficeKit* m_pOffice;
50 : LibreOfficeKitDocument* m_pDocument;
51 : long m_nDocumentWidthTwips;
52 : long m_nDocumentHeightTwips;
53 : /// View or edit mode.
54 : bool m_bEdit;
55 : /// Position and size of the visible cursor.
56 : GdkRectangle m_aVisibleCursor;
57 : /// Cursor overlay is visible or hidden (for blinking).
58 : bool m_bCursorOverlayVisible;
59 : /// Cursor is visible or hidden (e.g. for graphic selection).
60 : bool m_bCursorVisible;
61 : /// Time of the last button press.
62 : guint32 m_nLastButtonPressTime;
63 : /// Time of the last button release.
64 : guint32 m_nLastButtonReleaseTime;
65 : /// Rectangles of the current text selection.
66 : std::vector<GdkRectangle> m_aTextSelectionRectangles;
67 : /// Position and size of the selection start (as if there would be a cursor caret there).
68 : GdkRectangle m_aTextSelectionStart;
69 : /// Position and size of the selection end.
70 : GdkRectangle m_aTextSelectionEnd;
71 : GdkRectangle m_aGraphicSelection;
72 : bool m_bInDragGraphicSelection;
73 :
74 : /// @name Start/middle/end handle.
75 : ///@{
76 : /// Bitmap of the text selection start handle.
77 : cairo_surface_t* m_pHandleStart;
78 : /// Rectangle of the text selection start handle, to know if the user clicked on it or not
79 : GdkRectangle m_aHandleStartRect;
80 : /// If we are in the middle of a drag of the text selection end handle.
81 : bool m_bInDragStartHandle;
82 : /// Bitmap of the text selection middle handle.
83 : cairo_surface_t* m_pHandleMiddle;
84 : /// Rectangle of the text selection middle handle, to know if the user clicked on it or not
85 : GdkRectangle m_aHandleMiddleRect;
86 : /// If we are in the middle of a drag of the text selection middle handle.
87 : bool m_bInDragMiddleHandle;
88 : /// Bitmap of the text selection end handle.
89 : cairo_surface_t* m_pHandleEnd;
90 : /// Rectangle of the text selection end handle, to know if the user clicked on it or not
91 : GdkRectangle m_aHandleEndRect;
92 : /// If we are in the middle of a drag of the text selection end handle.
93 : bool m_bInDragEndHandle;
94 : ///@}
95 :
96 : /// @name Graphic handles.
97 : ///@{
98 : /// Bitmap of a graphic selection handle.
99 : cairo_surface_t* m_pGraphicHandle;
100 : /// Rectangle of a graphic selection handle, to know if the user clicked on it or not.
101 : GdkRectangle m_aGraphicHandleRects[8];
102 : /// If we are in the middle of a drag of a graphic selection handle.
103 : bool m_bInDragGraphicHandles[8];
104 : ///@}
105 :
106 : /// Callback data, allocated in lok_doc_view_callback_worker(), released in lok_doc_view_callback().
107 0 : struct CallbackData
108 : {
109 : int m_nType;
110 : std::string m_aPayload;
111 : LOKDocView* m_pDocView;
112 :
113 : CallbackData(int nType, const std::string& rPayload, LOKDocView* pDocView);
114 : };
115 :
116 :
117 : LOKDocView_Impl(LOKDocView* pDocView);
118 : ~LOKDocView_Impl();
119 : /// Connected to the destroy signal of LOKDocView, deletes its LOKDocView_Impl.
120 : static void destroy(LOKDocView* pDocView, gpointer pData);
121 : /// Connected to the expose-event of the GtkDrawingArea
122 : static void on_exposed(GtkWidget *widget, GdkEvent *event, gpointer user_data);
123 : /// Receives a key press or release event.
124 : void signalKey(GdkEventKey* pEvent);
125 : /**
126 : * The user drags the handle, which is below the cursor, but wants to move the
127 : * cursor accordingly.
128 : *
129 : * @param pHandle the rectangle of the handle
130 : * @param pEvent the motion event
131 : * @param pPoint the computed point (output parameter)
132 : */
133 : static void getDragPoint(GdkRectangle* pHandle, GdkEventButton* pEvent, GdkPoint* pPoint);
134 : /// Receives a button press event.
135 : static gboolean signalButton(GtkWidget* pEventBox, GdkEventButton* pEvent, LOKDocView* pDocView);
136 : /// Implementation of button press event handler, invoked by signalButton().
137 : gboolean signalButtonImpl(GdkEventButton* pEvent);
138 : /// Receives a motion event.
139 : static gboolean signalMotion(GtkWidget* pEventBox, GdkEventButton* pEvent, LOKDocView* pDocView);
140 : /// Implementation of motion event handler, invoked by signalMotion().
141 : gboolean signalMotionImpl(GdkEventButton* pEvent);
142 : /// Receives an expose event.
143 : static gboolean renderOverlay(GtkWidget* pWidget, GdkEventExpose* pEvent, LOKDocView* pDocView);
144 : /// Implementation of expose event handler (renders cursor and selection overlay), invoked by renderOverlay().
145 : gboolean renderOverlayImpl(GtkWidget* pEventBox);
146 : /// Is rRectangle empty?
147 : static bool isEmptyRectangle(const GdkRectangle& rRectangle);
148 : /**
149 : * Renders pHandle below an rCursor rectangle on pCairo.
150 : * @param rRectangle output parameter, the rectangle that contains the rendered handle.
151 : */
152 : void renderHandle(cairo_t* pCairo, const GdkRectangle& rCursor, cairo_surface_t* pHandle, GdkRectangle& rRectangle);
153 : /// Renders pHandle around an rSelection rectangle on pCairo.
154 : void renderGraphicHandle(cairo_t* pCairo, const GdkRectangle& rSelection, cairo_surface_t* pHandle);
155 : /// Takes care of the blinking cursor.
156 : static gboolean handleTimeout(gpointer pData);
157 : /// Implementation of the timeout handler, invoked by handleTimeout().
158 : gboolean handleTimeoutImpl();
159 : /**
160 : * Renders the document to a number of visible tiles.
161 : *
162 : * This method is invoked only manually, not when some Gtk signal is
163 : * emitted.
164 : *
165 : * @param pPartial if 0, then the full visible document is rendered, otherwise only
166 : * the tiles that intersect with pPartial.
167 : */
168 : void renderDocument(GdkRectangle* pPartial);
169 : /// Returns the GdkRectangle of a x,y,width,height string.
170 : GdkRectangle payloadToRectangle(const char* pPayload);
171 : /// Returns the GdkRectangles of a x1,y1,w1,h1;x2,y2,w2,h2;... string.
172 : std::vector<GdkRectangle> payloadToRectangles(const char* pPayload);
173 : /// Returns the string representation of a LibreOfficeKitCallbackType enumeration element.
174 : static const char* callbackTypeToString(int nType);
175 : /// Invoked on the main thread if callbackWorker() requests so.
176 : static gboolean callback(gpointer pData);
177 : /// Invoked on the main thread if globalCallbackWorker() requests so.
178 : static gboolean globalCallback(gpointer pData);
179 : /// Implementation of the callback handler, invoked by callback();
180 : gboolean callbackImpl(CallbackData* pCallbackData);
181 : /// Our LOK callback, runs on the LO thread.
182 : static void callbackWorker(int nType, const char* pPayload, void* pData);
183 : /// Implementation of the callback worder handler, invoked by callbackWorker().
184 : void callbackWorkerImpl(int nType, const char* pPayload);
185 : /// Our global LOK callback, runs on the LO thread.
186 : static void globalCallbackWorker(int nType, const char* pPayload, void* pData);
187 : /// Implementation of the global callback worder handler, invoked by globalCallbackWorker().
188 : void globalCallbackWorkerImpl(int nType, const char* pPayload);
189 : /// Command state (various buttons like bold are toggled or not) is changed.
190 : void commandChanged(const std::string& rPayload);
191 : /// Search did not find any matches.
192 : void searchNotFound(const std::string& rPayload);
193 : /// LOK decided to change parts, need to update UI.
194 : void setPart(const std::string& rPayload);
195 : /// Sets the tiles enclosed by rRectangle as invalid in m_aTileBuffer
196 : void setTilesInvalid(const GdkRectangle& rRectangle);
197 : };
198 :
199 : enum
200 : {
201 : EDIT_CHANGED,
202 : COMMAND_CHANGED,
203 : SEARCH_NOT_FOUND,
204 : PART_CHANGED,
205 : LAST_SIGNAL
206 : };
207 :
208 :
209 : static guint doc_view_signals[LAST_SIGNAL] = { 0 };
210 :
211 : SAL_DLLPUBLIC_EXPORT GType lok_doc_view_get_type();
212 : #ifdef __GNUC__
213 : #pragma GCC diagnostic push
214 : #pragma GCC diagnostic ignored "-Wunused-function"
215 : #endif
216 0 : G_DEFINE_TYPE(LOKDocView, lok_doc_view, GTK_TYPE_SCROLLED_WINDOW)
217 : #ifdef __GNUC__
218 : #pragma GCC diagnostic pop
219 : #endif
220 :
221 : namespace {
222 :
223 : /// Sets rWidth and rHeight from a "width, height" string.
224 0 : void payloadToSize(const char* pPayload, long& rWidth, long& rHeight)
225 : {
226 0 : rWidth = rHeight = 0;
227 0 : gchar** ppCoordinates = g_strsplit(pPayload, ", ", 2);
228 0 : gchar** ppCoordinate = ppCoordinates;
229 0 : if (!*ppCoordinate)
230 0 : return;
231 0 : rWidth = atoi(*ppCoordinate);
232 0 : ++ppCoordinate;
233 0 : if (!*ppCoordinate)
234 0 : return;
235 0 : rHeight = atoi(*ppCoordinate);
236 0 : g_strfreev(ppCoordinates);
237 : }
238 :
239 : }
240 :
241 :
242 :
243 : namespace {
244 :
245 : /// Implementation of the global callback handler, invoked by globalCallback();
246 0 : gboolean globalCallbackImpl(LOKDocView_Impl::CallbackData* pCallback)
247 : {
248 0 : switch (pCallback->m_nType)
249 : {
250 : case LOK_CALLBACK_STATUS_INDICATOR_START:
251 : {
252 : }
253 0 : break;
254 : case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
255 : {
256 : }
257 0 : break;
258 : case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
259 : {
260 : }
261 0 : break;
262 : default:
263 0 : g_assert(false);
264 : break;
265 : }
266 0 : delete pCallback;
267 :
268 0 : return G_SOURCE_REMOVE;
269 : }
270 :
271 : }
272 :
273 0 : LOKDocView_Impl::CallbackData::CallbackData(int nType, const std::string& rPayload, LOKDocView* pDocView)
274 : : m_nType(nType),
275 : m_aPayload(rPayload),
276 0 : m_pDocView(pDocView)
277 : {
278 0 : }
279 :
280 0 : LOKDocView_Impl::LOKDocView_Impl(LOKDocView* pDocView)
281 : : m_pDocView(pDocView),
282 0 : m_pDrawingArea(gtk_drawing_area_new()),
283 : m_aTileBuffer(TileBuffer(0,0)),
284 : m_fZoom(1),
285 : m_pOffice(0),
286 : m_pDocument(0),
287 : m_nDocumentWidthTwips(0),
288 : m_nDocumentHeightTwips(0),
289 : m_bEdit(false),
290 : m_aVisibleCursor({0, 0, 0, 0}),
291 : m_bCursorOverlayVisible(false),
292 : m_bCursorVisible(true),
293 : m_nLastButtonPressTime(0),
294 : m_nLastButtonReleaseTime(0),
295 : m_aTextSelectionStart({0, 0, 0, 0}),
296 : m_aTextSelectionEnd({0, 0, 0, 0}),
297 : m_aGraphicSelection({0, 0, 0, 0}),
298 : m_bInDragGraphicSelection(false),
299 :
300 : // Start/middle/end handle.
301 : m_pHandleStart(0),
302 : m_aHandleStartRect({0, 0, 0, 0}),
303 : m_bInDragStartHandle(false),
304 : m_pHandleMiddle(0),
305 : m_aHandleMiddleRect({0, 0, 0, 0}),
306 : m_bInDragMiddleHandle(false),
307 : m_pHandleEnd(0),
308 : m_aHandleEndRect({0, 0, 0, 0}),
309 : m_bInDragEndHandle(false),
310 :
311 0 : m_pGraphicHandle(0)
312 : {
313 0 : memset(&m_aGraphicHandleRects, 0, sizeof(m_aGraphicHandleRects));
314 0 : memset(&m_bInDragGraphicHandles, 0, sizeof(m_bInDragGraphicHandles));
315 0 : }
316 :
317 0 : LOKDocView_Impl::~LOKDocView_Impl()
318 : {
319 0 : if (m_pDocument)
320 0 : m_pDocument->pClass->destroy(m_pDocument);
321 0 : m_pDocument = 0;
322 0 : }
323 :
324 0 : void LOKDocView_Impl::destroy(LOKDocView* pDocView, gpointer /*pData*/)
325 : {
326 : // We specifically need to destroy the document when closing in order to ensure
327 : // that lock files etc. are cleaned up.
328 0 : delete pDocView->m_pImpl;
329 0 : }
330 :
331 0 : void LOKDocView_Impl::on_exposed(GtkWidget* /*widget*/, GdkEvent* /*event*/, gpointer userdata)
332 : {
333 0 : LOKDocView *pDocView = LOK_DOC_VIEW (userdata);
334 0 : pDocView->m_pImpl->renderDocument(0);
335 0 : }
336 :
337 0 : void LOKDocView_Impl::signalKey(GdkEventKey* pEvent)
338 : {
339 0 : int nCharCode = 0;
340 0 : int nKeyCode = 0;
341 :
342 0 : if (!m_bEdit)
343 : {
344 0 : g_info("signalKey: not in edit mode, ignore");
345 0 : return;
346 : }
347 :
348 0 : switch (pEvent->keyval)
349 : {
350 : case GDK_BackSpace:
351 0 : nKeyCode = com::sun::star::awt::Key::BACKSPACE;
352 0 : break;
353 : case GDK_Return:
354 0 : nKeyCode = com::sun::star::awt::Key::RETURN;
355 0 : break;
356 : case GDK_Escape:
357 0 : nKeyCode = com::sun::star::awt::Key::ESCAPE;
358 0 : break;
359 : case GDK_Tab:
360 0 : nKeyCode = com::sun::star::awt::Key::TAB;
361 0 : break;
362 : case GDK_Down:
363 0 : nKeyCode = com::sun::star::awt::Key::DOWN;
364 0 : break;
365 : case GDK_Up:
366 0 : nKeyCode = com::sun::star::awt::Key::UP;
367 0 : break;
368 : case GDK_Left:
369 0 : nKeyCode = com::sun::star::awt::Key::LEFT;
370 0 : break;
371 : case GDK_Right:
372 0 : nKeyCode = com::sun::star::awt::Key::RIGHT;
373 0 : break;
374 : default:
375 0 : if (pEvent->keyval >= GDK_F1 && pEvent->keyval <= GDK_F26)
376 0 : nKeyCode = com::sun::star::awt::Key::F1 + (pEvent->keyval - GDK_F1);
377 : else
378 0 : nCharCode = gdk_keyval_to_unicode(pEvent->keyval);
379 : }
380 :
381 : // rsc is not public API, but should be good enough for debugging purposes.
382 : // If this is needed for real, then probably a new param of type
383 : // css::awt::KeyModifier is needed in postKeyEvent().
384 0 : if (pEvent->state & GDK_SHIFT_MASK)
385 0 : nKeyCode |= KEY_SHIFT;
386 :
387 0 : if (pEvent->type == GDK_KEY_RELEASE)
388 0 : m_pDocument->pClass->postKeyEvent(m_pDocument, LOK_KEYEVENT_KEYUP, nCharCode, nKeyCode);
389 : else
390 0 : m_pDocument->pClass->postKeyEvent(m_pDocument, LOK_KEYEVENT_KEYINPUT, nCharCode, nKeyCode);
391 : }
392 :
393 0 : gboolean LOKDocView_Impl::signalButton(GtkWidget* /*pEventBox*/, GdkEventButton* pEvent, LOKDocView* pDocView)
394 : {
395 0 : return pDocView->m_pImpl->signalButtonImpl(pEvent);
396 : }
397 :
398 : /// Receives a button press event.
399 0 : gboolean LOKDocView_Impl::signalButtonImpl(GdkEventButton* pEvent)
400 : {
401 0 : g_info("LOKDocView_Impl::signalButton: %d, %d (in twips: %d, %d)", (int)pEvent->x, (int)pEvent->y, (int)pixelToTwip(pEvent->x, m_fZoom), (int)pixelToTwip(pEvent->y, m_fZoom));
402 :
403 0 : if (pEvent->type == GDK_BUTTON_RELEASE)
404 : {
405 0 : if (m_bInDragStartHandle)
406 : {
407 0 : g_info("LOKDocView_Impl::signalButton: end of drag start handle");
408 0 : m_bInDragStartHandle = false;
409 0 : return FALSE;
410 : }
411 0 : else if (m_bInDragMiddleHandle)
412 : {
413 0 : g_info("LOKDocView_Impl::signalButton: end of drag middle handle");
414 0 : m_bInDragMiddleHandle = false;
415 0 : return FALSE;
416 : }
417 0 : else if (m_bInDragEndHandle)
418 : {
419 0 : g_info("LOKDocView_Impl::signalButton: end of drag end handle");
420 0 : m_bInDragEndHandle = false;
421 0 : return FALSE;
422 : }
423 :
424 0 : for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
425 : {
426 0 : if (m_bInDragGraphicHandles[i])
427 : {
428 0 : g_info("LOKDocView_Impl::signalButton: end of drag graphic handle #%d", i);
429 0 : m_bInDragGraphicHandles[i] = false;
430 0 : m_pDocument->pClass->setGraphicSelection(m_pDocument, LOK_SETGRAPHICSELECTION_END, pixelToTwip(pEvent->x, m_fZoom), pixelToTwip(pEvent->y, m_fZoom));
431 0 : return FALSE;
432 : }
433 : }
434 :
435 0 : if (m_bInDragGraphicSelection)
436 : {
437 0 : g_info("LOKDocView_Impl::signalButton: end of drag graphic selection");
438 0 : m_bInDragGraphicSelection = false;
439 0 : m_pDocument->pClass->setGraphicSelection(m_pDocument, LOK_SETGRAPHICSELECTION_END, pixelToTwip(pEvent->x, m_fZoom), pixelToTwip(pEvent->y, m_fZoom));
440 0 : return FALSE;
441 : }
442 : }
443 :
444 0 : if (m_bEdit)
445 : {
446 : GdkRectangle aClick;
447 0 : aClick.x = pEvent->x;
448 0 : aClick.y = pEvent->y;
449 0 : aClick.width = 1;
450 0 : aClick.height = 1;
451 0 : if (pEvent->type == GDK_BUTTON_PRESS)
452 : {
453 0 : if (gdk_rectangle_intersect(&aClick, &m_aHandleStartRect, NULL))
454 : {
455 0 : g_info("LOKDocView_Impl::signalButton: start of drag start handle");
456 0 : m_bInDragStartHandle = true;
457 0 : return FALSE;
458 : }
459 0 : else if (gdk_rectangle_intersect(&aClick, &m_aHandleMiddleRect, NULL))
460 : {
461 0 : g_info("LOKDocView_Impl::signalButton: start of drag middle handle");
462 0 : m_bInDragMiddleHandle = true;
463 0 : return FALSE;
464 : }
465 0 : else if (gdk_rectangle_intersect(&aClick, &m_aHandleEndRect, NULL))
466 : {
467 0 : g_info("LOKDocView_Impl::signalButton: start of drag end handle");
468 0 : m_bInDragEndHandle = true;
469 0 : return FALSE;
470 : }
471 :
472 0 : for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
473 : {
474 0 : if (gdk_rectangle_intersect(&aClick, &m_aGraphicHandleRects[i], NULL))
475 : {
476 0 : g_info("LOKDocView_Impl::signalButton: start of drag graphic handle #%d", i);
477 0 : m_bInDragGraphicHandles[i] = true;
478 : m_pDocument->pClass->setGraphicSelection(m_pDocument,
479 : LOK_SETGRAPHICSELECTION_START,
480 0 : pixelToTwip(m_aGraphicHandleRects[i].x + m_aGraphicHandleRects[i].width / 2, m_fZoom),
481 0 : pixelToTwip(m_aGraphicHandleRects[i].y + m_aGraphicHandleRects[i].height / 2, m_fZoom));
482 0 : return FALSE;
483 : }
484 : }
485 : }
486 : }
487 :
488 0 : if (!m_bEdit)
489 0 : lok_doc_view_set_edit(m_pDocView, TRUE);
490 :
491 0 : switch (pEvent->type)
492 : {
493 : case GDK_BUTTON_PRESS:
494 : {
495 0 : int nCount = 1;
496 0 : if ((pEvent->time - m_nLastButtonPressTime) < 250)
497 0 : nCount++;
498 0 : m_nLastButtonPressTime = pEvent->time;
499 0 : m_pDocument->pClass->postMouseEvent(m_pDocument, LOK_MOUSEEVENT_MOUSEBUTTONDOWN, pixelToTwip(pEvent->x, m_fZoom), pixelToTwip(pEvent->y, m_fZoom), nCount);
500 0 : break;
501 : }
502 : case GDK_BUTTON_RELEASE:
503 : {
504 0 : int nCount = 1;
505 0 : if ((pEvent->time - m_nLastButtonReleaseTime) < 250)
506 0 : nCount++;
507 0 : m_nLastButtonReleaseTime = pEvent->time;
508 0 : m_pDocument->pClass->postMouseEvent(m_pDocument, LOK_MOUSEEVENT_MOUSEBUTTONUP, pixelToTwip(pEvent->x, m_fZoom), pixelToTwip(pEvent->y, m_fZoom), nCount);
509 0 : break;
510 : }
511 : default:
512 0 : break;
513 : }
514 0 : return FALSE;
515 : }
516 :
517 0 : void LOKDocView_Impl::getDragPoint(GdkRectangle* pHandle, GdkEventButton* pEvent, GdkPoint* pPoint)
518 : {
519 : GdkPoint aCursor, aHandle;
520 :
521 : // Center of the cursor rectangle: we know that it's above the handle.
522 0 : aCursor.x = pHandle->x + pHandle->width / 2;
523 0 : aCursor.y = pHandle->y - pHandle->height / 2;
524 : // Center of the handle rectangle.
525 0 : aHandle.x = pHandle->x + pHandle->width / 2;
526 0 : aHandle.y = pHandle->y + pHandle->height / 2;
527 : // Our target is the original cursor position + the dragged offset.
528 0 : pPoint->x = aCursor.x + (pEvent->x - aHandle.x);
529 0 : pPoint->y = aCursor.y + (pEvent->y - aHandle.y);
530 0 : }
531 :
532 0 : gboolean LOKDocView_Impl::signalMotion(GtkWidget* /*pEventBox*/, GdkEventButton* pEvent, LOKDocView* pDocView)
533 : {
534 0 : return pDocView->m_pImpl->signalMotionImpl(pEvent);
535 : }
536 :
537 0 : gboolean LOKDocView_Impl::signalMotionImpl(GdkEventButton* pEvent)
538 : {
539 : GdkPoint aPoint;
540 :
541 0 : if (m_bInDragMiddleHandle)
542 : {
543 0 : g_info("lcl_signalMotion: dragging the middle handle");
544 0 : LOKDocView_Impl::getDragPoint(&m_aHandleMiddleRect, pEvent, &aPoint);
545 0 : m_pDocument->pClass->setTextSelection(m_pDocument, LOK_SETTEXTSELECTION_RESET, pixelToTwip(aPoint.x, m_fZoom), pixelToTwip(aPoint.y, m_fZoom));
546 0 : return FALSE;
547 : }
548 0 : if (m_bInDragStartHandle)
549 : {
550 0 : g_info("lcl_signalMotion: dragging the start handle");
551 0 : LOKDocView_Impl::getDragPoint(&m_aHandleStartRect, pEvent, &aPoint);
552 0 : m_pDocument->pClass->setTextSelection(m_pDocument, LOK_SETTEXTSELECTION_START, pixelToTwip(aPoint.x, m_fZoom), pixelToTwip(aPoint.y, m_fZoom));
553 0 : return FALSE;
554 : }
555 0 : if (m_bInDragEndHandle)
556 : {
557 0 : g_info("lcl_signalMotion: dragging the end handle");
558 0 : LOKDocView_Impl::getDragPoint(&m_aHandleEndRect, pEvent, &aPoint);
559 0 : m_pDocument->pClass->setTextSelection(m_pDocument, LOK_SETTEXTSELECTION_END, pixelToTwip(aPoint.x, m_fZoom), pixelToTwip(aPoint.y, m_fZoom));
560 0 : return FALSE;
561 : }
562 0 : for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
563 : {
564 0 : if (m_bInDragGraphicHandles[i])
565 : {
566 0 : g_info("lcl_signalMotion: dragging the graphic handle #%d", i);
567 0 : return FALSE;
568 : }
569 : }
570 0 : if (m_bInDragGraphicSelection)
571 : {
572 0 : g_info("lcl_signalMotion: dragging the graphic selection");
573 0 : return FALSE;
574 : }
575 :
576 : GdkRectangle aMotionInTwipsInTwips;
577 0 : aMotionInTwipsInTwips.x = pixelToTwip(pEvent->x, m_fZoom);
578 0 : aMotionInTwipsInTwips.y = pixelToTwip(pEvent->y, m_fZoom);
579 0 : aMotionInTwipsInTwips.width = 1;
580 0 : aMotionInTwipsInTwips.height = 1;
581 0 : if (gdk_rectangle_intersect(&aMotionInTwipsInTwips, &m_aGraphicSelection, 0))
582 : {
583 0 : g_info("lcl_signalMotion: start of drag graphic selection");
584 0 : m_bInDragGraphicSelection = true;
585 0 : m_pDocument->pClass->setGraphicSelection(m_pDocument, LOK_SETGRAPHICSELECTION_START, pixelToTwip(pEvent->x, m_fZoom), pixelToTwip(pEvent->y, m_fZoom));
586 0 : return FALSE;
587 : }
588 :
589 : // Otherwise a mouse move, as on the desktop.
590 0 : m_pDocument->pClass->postMouseEvent(m_pDocument, LOK_MOUSEEVENT_MOUSEMOVE, pixelToTwip(pEvent->x, m_fZoom), pixelToTwip(pEvent->y, m_fZoom), 1);
591 :
592 0 : return FALSE;
593 : }
594 :
595 0 : gboolean LOKDocView_Impl::renderOverlay(GtkWidget* pEventBox, GdkEventExpose* /*pEvent*/, LOKDocView* pDocView)
596 : {
597 0 : return pDocView->m_pImpl->renderOverlayImpl(pEventBox);
598 : }
599 :
600 0 : gboolean LOKDocView_Impl::renderOverlayImpl(GtkWidget* pWidget)
601 : {
602 : #if GTK_CHECK_VERSION(2,14,0) // we need gtk_widget_get_window()
603 0 : cairo_t* pCairo = gdk_cairo_create(gtk_widget_get_window(pWidget));
604 :
605 0 : if (m_bEdit && m_bCursorVisible && m_bCursorOverlayVisible && !isEmptyRectangle(m_aVisibleCursor))
606 : {
607 0 : if (m_aVisibleCursor.width < 30)
608 : // Set a minimal width if it would be 0.
609 0 : m_aVisibleCursor.width = 30;
610 :
611 0 : cairo_set_source_rgb(pCairo, 0, 0, 0);
612 : cairo_rectangle(pCairo,
613 0 : twipToPixel(m_aVisibleCursor.x, m_fZoom),
614 0 : twipToPixel(m_aVisibleCursor.y, m_fZoom),
615 0 : twipToPixel(m_aVisibleCursor.width, m_fZoom),
616 0 : twipToPixel(m_aVisibleCursor.height, m_fZoom));
617 0 : cairo_fill(pCairo);
618 : }
619 :
620 0 : if (m_bEdit && m_bCursorVisible && !isEmptyRectangle(m_aVisibleCursor) && m_aTextSelectionRectangles.empty())
621 : {
622 : // Have a cursor, but no selection: we need the middle handle.
623 0 : if (!m_pHandleMiddle)
624 0 : m_pHandleMiddle = cairo_image_surface_create_from_png(CURSOR_HANDLE_DIR "handle_middle.png");
625 0 : renderHandle(pCairo, m_aVisibleCursor, m_pHandleMiddle, m_aHandleMiddleRect);
626 : }
627 :
628 0 : if (!m_aTextSelectionRectangles.empty())
629 : {
630 0 : for (GdkRectangle& rRectangle : m_aTextSelectionRectangles)
631 : {
632 : // Blue with 75% transparency.
633 0 : cairo_set_source_rgba(pCairo, ((double)0x43)/255, ((double)0xac)/255, ((double)0xe8)/255, 0.25);
634 : cairo_rectangle(pCairo,
635 0 : twipToPixel(rRectangle.x, m_fZoom),
636 0 : twipToPixel(rRectangle.y, m_fZoom),
637 0 : twipToPixel(rRectangle.width, m_fZoom),
638 0 : twipToPixel(rRectangle.height, m_fZoom));
639 0 : cairo_fill(pCairo);
640 : }
641 :
642 : // Handles
643 0 : if (!isEmptyRectangle(m_aTextSelectionStart))
644 : {
645 : // Have a start position: we need a start handle.
646 0 : if (!m_pHandleStart)
647 0 : m_pHandleStart = cairo_image_surface_create_from_png(CURSOR_HANDLE_DIR "handle_start.png");
648 0 : renderHandle(pCairo, m_aTextSelectionStart, m_pHandleStart, m_aHandleStartRect);
649 : }
650 0 : if (!isEmptyRectangle(m_aTextSelectionEnd))
651 : {
652 : // Have a start position: we need an end handle.
653 0 : if (!m_pHandleEnd)
654 0 : m_pHandleEnd = cairo_image_surface_create_from_png(CURSOR_HANDLE_DIR "handle_end.png");
655 0 : renderHandle(pCairo, m_aTextSelectionEnd, m_pHandleEnd, m_aHandleEndRect);
656 : }
657 : }
658 :
659 0 : if (!isEmptyRectangle(m_aGraphicSelection))
660 : {
661 0 : if (!m_pGraphicHandle)
662 0 : m_pGraphicHandle = cairo_image_surface_create_from_png(CURSOR_HANDLE_DIR "handle_graphic.png");
663 0 : renderGraphicHandle(pCairo, m_aGraphicSelection, m_pGraphicHandle);
664 : }
665 :
666 0 : cairo_destroy(pCairo);
667 : #endif
668 0 : return FALSE;
669 : }
670 :
671 0 : bool LOKDocView_Impl::isEmptyRectangle(const GdkRectangle& rRectangle)
672 : {
673 0 : return rRectangle.x == 0 && rRectangle.y == 0 && rRectangle.width == 0 && rRectangle.height == 0;
674 : }
675 :
676 0 : void LOKDocView_Impl::setTilesInvalid(const GdkRectangle& rRectangle)
677 : {
678 : GdkRectangle aRectanglePixels;
679 : GdkPoint aStart, aEnd;
680 :
681 0 : aRectanglePixels.x = twipToPixel(rRectangle.x, m_fZoom);
682 0 : aRectanglePixels.y = twipToPixel(rRectangle.y, m_fZoom);
683 0 : aRectanglePixels.width = twipToPixel(rRectangle.width, m_fZoom);
684 0 : aRectanglePixels.height = twipToPixel(rRectangle.height, m_fZoom);
685 :
686 0 : aStart.x = aRectanglePixels.y / nTileSizePixels;
687 0 : aStart.y = aRectanglePixels.x / nTileSizePixels;
688 0 : aEnd.x = (aRectanglePixels.y + aRectanglePixels.height + nTileSizePixels) / nTileSizePixels;
689 0 : aEnd.y = (aRectanglePixels.x + aRectanglePixels.width + nTileSizePixels) / nTileSizePixels;
690 :
691 0 : for (int i = aStart.x; i < aEnd.x; i++)
692 0 : for (int j = aStart.y; j < aEnd.y; j++)
693 0 : m_aTileBuffer.setInvalid(i, j);
694 0 : }
695 :
696 0 : void LOKDocView_Impl::renderHandle(cairo_t* pCairo, const GdkRectangle& rCursor, cairo_surface_t* pHandle, GdkRectangle& rRectangle)
697 : {
698 : GdkPoint aCursorBottom;
699 : int nHandleWidth, nHandleHeight;
700 : double fHandleScale;
701 :
702 0 : nHandleWidth = cairo_image_surface_get_width(pHandle);
703 0 : nHandleHeight = cairo_image_surface_get_height(pHandle);
704 : // We want to scale down the handle, so that its height is the same as the cursor caret.
705 0 : fHandleScale = twipToPixel(rCursor.height, m_fZoom) / nHandleHeight;
706 : // We want the top center of the handle bitmap to be at the bottom center of the cursor rectangle.
707 0 : aCursorBottom.x = twipToPixel(rCursor.x, m_fZoom) + twipToPixel(rCursor.width, m_fZoom) / 2 - (nHandleWidth * fHandleScale) / 2;
708 0 : aCursorBottom.y = twipToPixel(rCursor.y, m_fZoom) + twipToPixel(rCursor.height, m_fZoom);
709 0 : cairo_save(pCairo);
710 0 : cairo_translate(pCairo, aCursorBottom.x, aCursorBottom.y);
711 0 : cairo_scale(pCairo, fHandleScale, fHandleScale);
712 0 : cairo_set_source_surface(pCairo, pHandle, 0, 0);
713 0 : cairo_paint(pCairo);
714 0 : cairo_restore(pCairo);
715 :
716 0 : rRectangle.x = aCursorBottom.x;
717 0 : rRectangle.y = aCursorBottom.y;
718 0 : rRectangle.width = nHandleWidth * fHandleScale;
719 0 : rRectangle.height = nHandleHeight * fHandleScale;
720 0 : }
721 :
722 : /// Renders pHandle around an rSelection rectangle on pCairo.
723 0 : void LOKDocView_Impl::renderGraphicHandle(cairo_t* pCairo, const GdkRectangle& rSelection, cairo_surface_t* pHandle)
724 : {
725 : int nHandleWidth, nHandleHeight;
726 : GdkRectangle aSelection;
727 :
728 0 : nHandleWidth = cairo_image_surface_get_width(pHandle);
729 0 : nHandleHeight = cairo_image_surface_get_height(pHandle);
730 :
731 0 : aSelection.x = twipToPixel(rSelection.x, m_fZoom);
732 0 : aSelection.y = twipToPixel(rSelection.y, m_fZoom);
733 0 : aSelection.width = twipToPixel(rSelection.width, m_fZoom);
734 0 : aSelection.height = twipToPixel(rSelection.height, m_fZoom);
735 :
736 0 : for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
737 : {
738 0 : int x = aSelection.x, y = aSelection.y;
739 0 : cairo_save(pCairo);
740 :
741 0 : switch (i)
742 : {
743 : case 0: // top-left
744 0 : break;
745 : case 1: // top-middle
746 0 : x += aSelection.width / 2;
747 0 : break;
748 : case 2: // top-right
749 0 : x += aSelection.width;
750 0 : break;
751 : case 3: // middle-left
752 0 : y += aSelection.height / 2;
753 0 : break;
754 : case 4: // middle-right
755 0 : x += aSelection.width;
756 0 : y += aSelection.height / 2;
757 0 : break;
758 : case 5: // bottom-left
759 0 : y += aSelection.height;
760 0 : break;
761 : case 6: // bottom-middle
762 0 : x += aSelection.width / 2;
763 0 : y += aSelection.height;
764 0 : break;
765 : case 7: // bottom-right
766 0 : x += aSelection.width;
767 0 : y += aSelection.height;
768 0 : break;
769 : }
770 :
771 : // Center the handle.
772 0 : x -= nHandleWidth / 2;
773 0 : y -= nHandleHeight / 2;
774 :
775 0 : m_aGraphicHandleRects[i].x = x;
776 0 : m_aGraphicHandleRects[i].y = y;
777 0 : m_aGraphicHandleRects[i].width = nHandleWidth;
778 0 : m_aGraphicHandleRects[i].height = nHandleHeight;
779 :
780 0 : cairo_translate(pCairo, x, y);
781 0 : cairo_set_source_surface(pCairo, pHandle, 0, 0);
782 0 : cairo_paint(pCairo);
783 0 : cairo_restore(pCairo);
784 : }
785 0 : }
786 :
787 0 : gboolean LOKDocView_Impl::handleTimeout(gpointer pData)
788 : {
789 0 : LOKDocView* pDocView = static_cast<LOKDocView*>(pData);
790 0 : return pDocView->m_pImpl->handleTimeoutImpl();
791 : }
792 :
793 0 : gboolean LOKDocView_Impl::handleTimeoutImpl()
794 : {
795 0 : if (m_bEdit)
796 : {
797 0 : if (m_bCursorOverlayVisible)
798 0 : m_bCursorOverlayVisible = false;
799 : else
800 0 : m_bCursorOverlayVisible = true;
801 0 : gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
802 : }
803 :
804 0 : return G_SOURCE_CONTINUE;
805 : }
806 :
807 0 : void LOKDocView_Impl::renderDocument(GdkRectangle* pPartial)
808 : {
809 : GdkRectangle visibleArea;
810 0 : lok_doc_view_get_visarea (m_pDocView, &visibleArea);
811 :
812 0 : long nDocumentWidthPixels = twipToPixel(m_nDocumentWidthTwips, m_fZoom);
813 0 : long nDocumentHeightPixels = twipToPixel(m_nDocumentHeightTwips, m_fZoom);
814 : // Total number of rows / columns in this document.
815 0 : guint nRows = ceil((double)nDocumentHeightPixels / nTileSizePixels);
816 0 : guint nColumns = ceil((double)nDocumentWidthPixels / nTileSizePixels);
817 :
818 0 : cairo_t *pcairo = gdk_cairo_create(m_pDrawingArea->window);
819 :
820 : // Render the tiles.
821 0 : for (guint nRow = 0; nRow < nRows; ++nRow)
822 : {
823 0 : for (guint nColumn = 0; nColumn < nColumns; ++nColumn)
824 : {
825 : GdkRectangle aTileRectangleTwips, aTileRectanglePixels;
826 0 : bool bPaint = true;
827 :
828 : // Determine size of the tile: the rightmost/bottommost tiles may
829 : // be smaller, and we need the size to decide if we need to repaint.
830 0 : if (nColumn == nColumns - 1)
831 0 : aTileRectanglePixels.width = nDocumentWidthPixels - nColumn * nTileSizePixels;
832 : else
833 0 : aTileRectanglePixels.width = nTileSizePixels;
834 0 : if (nRow == nRows - 1)
835 0 : aTileRectanglePixels.height = nDocumentHeightPixels - nRow * nTileSizePixels;
836 : else
837 0 : aTileRectanglePixels.height = nTileSizePixels;
838 :
839 : // Determine size and position of the tile in document coordinates,
840 : // so we can decide if we can skip painting for partial rendering.
841 0 : aTileRectangleTwips.x = pixelToTwip(nTileSizePixels, m_fZoom) * nColumn;
842 0 : aTileRectangleTwips.y = pixelToTwip(nTileSizePixels, m_fZoom) * nRow;
843 0 : aTileRectangleTwips.width = pixelToTwip(aTileRectanglePixels.width, m_fZoom);
844 0 : aTileRectangleTwips.height = pixelToTwip(aTileRectanglePixels.height, m_fZoom);
845 0 : if (pPartial && !gdk_rectangle_intersect(pPartial, &aTileRectangleTwips, 0))
846 0 : bPaint = false;
847 :
848 0 : if (!gdk_rectangle_intersect(&visibleArea, &aTileRectangleTwips, 0))
849 0 : bPaint = false;
850 :
851 0 : if (bPaint)
852 : {
853 : //g_info("tile_buffer_get_tile (%d, %d)", nRow, nColumn);
854 :
855 0 : Tile& currentTile = m_aTileBuffer.getTile(nRow, nColumn, m_fZoom);
856 0 : GdkPixbuf* pPixBuf = currentTile.getBuffer();
857 :
858 : gdk_cairo_set_source_pixbuf (pcairo, pPixBuf,
859 0 : twipToPixel(aTileRectangleTwips.x, m_fZoom),
860 0 : twipToPixel(aTileRectangleTwips.y, m_fZoom));
861 0 : cairo_paint(pcairo);
862 : }
863 : }
864 : }
865 :
866 0 : cairo_destroy(pcairo);
867 0 : }
868 :
869 :
870 0 : GdkRectangle LOKDocView_Impl::payloadToRectangle(const char* pPayload)
871 : {
872 : GdkRectangle aRet;
873 :
874 0 : aRet.width = aRet.height = aRet.x = aRet.y = 0;
875 0 : gchar** ppCoordinates = g_strsplit(pPayload, ", ", 4);
876 0 : gchar** ppCoordinate = ppCoordinates;
877 0 : if (!*ppCoordinate)
878 0 : return aRet;
879 0 : aRet.x = atoi(*ppCoordinate);
880 0 : if (aRet.x < 0)
881 0 : aRet.x = 0;
882 0 : ++ppCoordinate;
883 0 : if (!*ppCoordinate)
884 0 : return aRet;
885 0 : aRet.y = atoi(*ppCoordinate);
886 0 : if (aRet.y < 0)
887 0 : aRet.y = 0;
888 0 : ++ppCoordinate;
889 0 : if (!*ppCoordinate)
890 0 : return aRet;
891 0 : aRet.width = atoi(*ppCoordinate);
892 0 : if (aRet.x + aRet.width > m_nDocumentWidthTwips)
893 0 : aRet.width = m_nDocumentWidthTwips - aRet.x;
894 0 : ++ppCoordinate;
895 0 : if (!*ppCoordinate)
896 0 : return aRet;
897 0 : aRet.height = atoi(*ppCoordinate);
898 0 : if (aRet.y + aRet.height > m_nDocumentHeightTwips)
899 0 : aRet.height = m_nDocumentHeightTwips - aRet.y;
900 0 : g_strfreev(ppCoordinates);
901 0 : return aRet;
902 : }
903 :
904 0 : std::vector<GdkRectangle> LOKDocView_Impl::payloadToRectangles(const char* pPayload)
905 : {
906 0 : std::vector<GdkRectangle> aRet;
907 :
908 0 : gchar** ppRectangles = g_strsplit(pPayload, "; ", 0);
909 0 : for (gchar** ppRectangle = ppRectangles; *ppRectangle; ++ppRectangle)
910 0 : aRet.push_back(payloadToRectangle(*ppRectangle));
911 0 : g_strfreev(ppRectangles);
912 :
913 0 : return aRet;
914 : }
915 :
916 : /// Returns the string representation of a LibreOfficeKitCallbackType enumeration element.
917 0 : const char* LOKDocView_Impl::callbackTypeToString(int nType)
918 : {
919 0 : switch (nType)
920 : {
921 : case LOK_CALLBACK_INVALIDATE_TILES:
922 0 : return "LOK_CALLBACK_INVALIDATE_TILES";
923 : case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
924 0 : return "LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR";
925 : case LOK_CALLBACK_TEXT_SELECTION:
926 0 : return "LOK_CALLBACK_TEXT_SELECTION";
927 : case LOK_CALLBACK_TEXT_SELECTION_START:
928 0 : return "LOK_CALLBACK_TEXT_SELECTION_START";
929 : case LOK_CALLBACK_TEXT_SELECTION_END:
930 0 : return "LOK_CALLBACK_TEXT_SELECTION_END";
931 : case LOK_CALLBACK_CURSOR_VISIBLE:
932 0 : return "LOK_CALLBACK_CURSOR_VISIBLE";
933 : case LOK_CALLBACK_GRAPHIC_SELECTION:
934 0 : return "LOK_CALLBACK_GRAPHIC_SELECTION";
935 : case LOK_CALLBACK_HYPERLINK_CLICKED:
936 0 : return "LOK_CALLBACK_HYPERLINK_CLICKED";
937 : case LOK_CALLBACK_STATE_CHANGED:
938 0 : return "LOK_CALLBACK_STATE_CHANGED";
939 : case LOK_CALLBACK_STATUS_INDICATOR_START:
940 0 : return "LOK_CALLBACK_STATUS_INDICATOR_START";
941 : case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
942 0 : return "LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE";
943 : case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
944 0 : return "LOK_CALLBACK_STATUS_INDICATOR_FINISH";
945 : case LOK_CALLBACK_SEARCH_NOT_FOUND:
946 0 : return "LOK_CALLBACK_SEARCH_NOT_FOUND";
947 : case LOK_CALLBACK_DOCUMENT_SIZE_CHANGED:
948 0 : return "LOK_CALLBACK_DOCUMENT_SIZE_CHANGED";
949 : case LOK_CALLBACK_SET_PART:
950 0 : return "LOK_CALLBACK_SET_PART";
951 : }
952 0 : return 0;
953 : }
954 :
955 0 : gboolean LOKDocView_Impl::callback(gpointer pData)
956 : {
957 0 : LOKDocView_Impl::CallbackData* pCallback = static_cast<LOKDocView_Impl::CallbackData*>(pData);
958 0 : return pCallback->m_pDocView->m_pImpl->callbackImpl(pCallback);
959 : }
960 :
961 0 : gboolean LOKDocView_Impl::globalCallback(gpointer pData)
962 : {
963 0 : LOKDocView_Impl::CallbackData* pCallback = static_cast<LOKDocView_Impl::CallbackData*>(pData);
964 0 : return globalCallbackImpl(pCallback);
965 : }
966 :
967 0 : gboolean LOKDocView_Impl::callbackImpl(CallbackData* pCallback)
968 : {
969 0 : switch (pCallback->m_nType)
970 : {
971 : case LOK_CALLBACK_INVALIDATE_TILES:
972 : {
973 0 : if (pCallback->m_aPayload != "EMPTY")
974 : {
975 0 : GdkRectangle aRectangle = LOKDocView_Impl::payloadToRectangle(pCallback->m_aPayload.c_str());
976 0 : setTilesInvalid(aRectangle);
977 : }
978 : else
979 0 : m_aTileBuffer.resetAllTiles();
980 :
981 0 : gtk_widget_queue_draw(m_pDrawingArea);
982 : }
983 0 : break;
984 : case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
985 : {
986 0 : m_aVisibleCursor = LOKDocView_Impl::payloadToRectangle(pCallback->m_aPayload.c_str());
987 0 : m_bCursorOverlayVisible = true;
988 0 : gtk_widget_queue_draw(m_pDrawingArea);
989 : }
990 0 : break;
991 : case LOK_CALLBACK_TEXT_SELECTION:
992 : {
993 0 : m_aTextSelectionRectangles = LOKDocView_Impl::payloadToRectangles(pCallback->m_aPayload.c_str());
994 :
995 : // In case the selection is empty, then we get no LOK_CALLBACK_TEXT_SELECTION_START/END events.
996 0 : if (m_aTextSelectionRectangles.empty())
997 : {
998 0 : memset(&m_aTextSelectionStart, 0, sizeof(m_aTextSelectionStart));
999 0 : memset(&m_aHandleStartRect, 0, sizeof(m_aHandleStartRect));
1000 0 : memset(&m_aTextSelectionEnd, 0, sizeof(m_aTextSelectionEnd));
1001 0 : memset(&m_aHandleEndRect, 0, sizeof(m_aHandleEndRect));
1002 : }
1003 : else
1004 0 : memset(&m_aHandleMiddleRect, 0, sizeof(m_aHandleMiddleRect));
1005 : }
1006 0 : break;
1007 : case LOK_CALLBACK_TEXT_SELECTION_START:
1008 : {
1009 0 : m_aTextSelectionStart = LOKDocView_Impl::payloadToRectangle(pCallback->m_aPayload.c_str());
1010 : }
1011 0 : break;
1012 : case LOK_CALLBACK_TEXT_SELECTION_END:
1013 : {
1014 0 : m_aTextSelectionEnd = LOKDocView_Impl::payloadToRectangle(pCallback->m_aPayload.c_str());
1015 : }
1016 0 : break;
1017 : case LOK_CALLBACK_CURSOR_VISIBLE:
1018 : {
1019 0 : m_bCursorVisible = pCallback->m_aPayload == "true";
1020 : }
1021 0 : break;
1022 : case LOK_CALLBACK_GRAPHIC_SELECTION:
1023 : {
1024 0 : if (pCallback->m_aPayload != "EMPTY")
1025 0 : m_aGraphicSelection = LOKDocView_Impl::payloadToRectangle(pCallback->m_aPayload.c_str());
1026 : else
1027 0 : memset(&m_aGraphicSelection, 0, sizeof(m_aGraphicSelection));
1028 0 : gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
1029 : }
1030 0 : break;
1031 : case LOK_CALLBACK_HYPERLINK_CLICKED:
1032 : {
1033 0 : GError* pError = NULL;
1034 : #if GTK_CHECK_VERSION(2,14,0)
1035 0 : gtk_show_uri(NULL, pCallback->m_aPayload.c_str(), GDK_CURRENT_TIME, &pError);
1036 : #endif
1037 : }
1038 0 : break;
1039 : case LOK_CALLBACK_STATE_CHANGED:
1040 : {
1041 0 : commandChanged(pCallback->m_aPayload);
1042 : }
1043 0 : break;
1044 : case LOK_CALLBACK_SEARCH_NOT_FOUND:
1045 : {
1046 0 : searchNotFound(pCallback->m_aPayload);
1047 : }
1048 0 : break;
1049 : case LOK_CALLBACK_DOCUMENT_SIZE_CHANGED:
1050 : {
1051 0 : payloadToSize(pCallback->m_aPayload.c_str(), m_nDocumentWidthTwips, m_nDocumentHeightTwips);
1052 : gtk_widget_set_size_request(m_pDrawingArea,
1053 0 : twipToPixel(m_nDocumentWidthTwips, m_fZoom),
1054 0 : twipToPixel(m_nDocumentHeightTwips, m_fZoom));
1055 : }
1056 0 : break;
1057 : case LOK_CALLBACK_SET_PART:
1058 : {
1059 0 : setPart(pCallback->m_aPayload);
1060 : }
1061 0 : break;
1062 : default:
1063 0 : g_assert(false);
1064 : break;
1065 : }
1066 0 : delete pCallback;
1067 :
1068 0 : return G_SOURCE_REMOVE;
1069 : }
1070 :
1071 0 : void LOKDocView_Impl::callbackWorker(int nType, const char* pPayload, void* pData)
1072 : {
1073 0 : LOKDocView* pDocView = static_cast<LOKDocView*>(pData);
1074 0 : pDocView->m_pImpl->callbackWorkerImpl(nType, pPayload);
1075 0 : }
1076 :
1077 0 : void LOKDocView_Impl::globalCallbackWorker(int nType, const char* pPayload, void* pData)
1078 : {
1079 0 : LOKDocView* pDocView = static_cast<LOKDocView*>(pData);
1080 0 : pDocView->m_pImpl->globalCallbackWorkerImpl(nType, pPayload);
1081 0 : }
1082 :
1083 0 : void LOKDocView_Impl::callbackWorkerImpl(int nType, const char* pPayload)
1084 : {
1085 0 : LOKDocView_Impl::CallbackData* pCallback = new LOKDocView_Impl::CallbackData(nType, pPayload ? pPayload : "(nil)", m_pDocView);
1086 0 : g_info("lok_doc_view_callback_worker: %s, '%s'", LOKDocView_Impl::callbackTypeToString(nType), pPayload);
1087 : #if GTK_CHECK_VERSION(2,12,0)
1088 0 : gdk_threads_add_idle(LOKDocView_Impl::callback, pCallback);
1089 : #endif
1090 0 : }
1091 :
1092 0 : void LOKDocView_Impl::globalCallbackWorkerImpl(int nType, const char* pPayload)
1093 : {
1094 0 : LOKDocView_Impl::CallbackData* pCallback = new LOKDocView_Impl::CallbackData(nType, pPayload ? pPayload : "(nil)", m_pDocView);
1095 0 : g_info("LOKDocView_Impl::globalCallbackWorkerImpl: %s, '%s'", LOKDocView_Impl::callbackTypeToString(nType), pPayload);
1096 : #if GTK_CHECK_VERSION(2,12,0)
1097 0 : gdk_threads_add_idle(LOKDocView_Impl::globalCallback, pCallback);
1098 : #endif
1099 0 : }
1100 :
1101 :
1102 :
1103 0 : void LOKDocView_Impl::commandChanged(const std::string& rString)
1104 : {
1105 0 : g_signal_emit(m_pDocView, doc_view_signals[COMMAND_CHANGED], 0, rString.c_str());
1106 0 : }
1107 :
1108 0 : void LOKDocView_Impl::searchNotFound(const std::string& rString)
1109 : {
1110 0 : g_signal_emit(m_pDocView, doc_view_signals[SEARCH_NOT_FOUND], 0, rString.c_str());
1111 0 : }
1112 :
1113 0 : void LOKDocView_Impl::setPart(const std::string& rString)
1114 : {
1115 0 : g_signal_emit(m_pDocView, doc_view_signals[PART_CHANGED], 0, std::stoi(rString));
1116 0 : renderDocument(0);
1117 0 : }
1118 :
1119 0 : static void lok_doc_view_class_init (LOKDocViewClass* pClass)
1120 : {
1121 0 : GObjectClass *gobject_class = G_OBJECT_CLASS(pClass);
1122 0 : pClass->edit_changed = NULL;
1123 : doc_view_signals[EDIT_CHANGED] =
1124 : g_signal_new("edit-changed",
1125 : G_TYPE_FROM_CLASS (gobject_class),
1126 : G_SIGNAL_RUN_FIRST,
1127 : G_STRUCT_OFFSET (LOKDocViewClass, edit_changed),
1128 : NULL, NULL,
1129 : g_cclosure_marshal_VOID__BOOLEAN,
1130 : G_TYPE_NONE, 1,
1131 0 : G_TYPE_BOOLEAN);
1132 0 : pClass->command_changed = NULL;
1133 : doc_view_signals[COMMAND_CHANGED] =
1134 : g_signal_new("command-changed",
1135 : G_TYPE_FROM_CLASS(gobject_class),
1136 : G_SIGNAL_RUN_FIRST,
1137 : G_STRUCT_OFFSET(LOKDocViewClass, command_changed),
1138 : NULL, NULL,
1139 : g_cclosure_marshal_VOID__STRING,
1140 : G_TYPE_NONE, 1,
1141 0 : G_TYPE_STRING);
1142 0 : pClass->search_not_found = 0;
1143 : doc_view_signals[SEARCH_NOT_FOUND] =
1144 : g_signal_new("search-not-found",
1145 : G_TYPE_FROM_CLASS(gobject_class),
1146 : G_SIGNAL_RUN_FIRST,
1147 : G_STRUCT_OFFSET(LOKDocViewClass, search_not_found),
1148 : NULL, NULL,
1149 : g_cclosure_marshal_VOID__STRING,
1150 : G_TYPE_NONE, 1,
1151 0 : G_TYPE_STRING);
1152 0 : pClass->part_changed = 0;
1153 : doc_view_signals[PART_CHANGED] =
1154 : g_signal_new("part-changed",
1155 : G_TYPE_FROM_CLASS(gobject_class),
1156 : G_SIGNAL_RUN_FIRST,
1157 : G_STRUCT_OFFSET(LOKDocViewClass, part_changed),
1158 : NULL, NULL,
1159 : g_cclosure_marshal_VOID__INT,
1160 : G_TYPE_NONE, 1,
1161 0 : G_TYPE_INT);
1162 0 : }
1163 :
1164 0 : static void lok_doc_view_init (LOKDocView* pDocView)
1165 : {
1166 : // Gtk ScrolledWindow is apparently not fully initialised yet, we specifically
1167 : // have to set the [hv]adjustment to prevent GTK assertions from firing, see
1168 : // https://bugzilla.gnome.org/show_bug.cgi?id=438114 for more info.
1169 0 : gtk_scrolled_window_set_hadjustment( GTK_SCROLLED_WINDOW( pDocView ), NULL );
1170 0 : gtk_scrolled_window_set_vadjustment( GTK_SCROLLED_WINDOW( pDocView ), NULL );
1171 :
1172 0 : pDocView->m_pImpl = new LOKDocView_Impl(pDocView);
1173 0 : gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW(pDocView),
1174 0 : pDocView->m_pImpl->m_pDrawingArea );
1175 :
1176 0 : g_signal_connect(G_OBJECT(pDocView->m_pImpl->m_pDrawingArea),
1177 : "expose-event",
1178 0 : G_CALLBACK(LOKDocView_Impl::on_exposed), pDocView);
1179 0 : g_signal_connect(G_OBJECT(pDocView->m_pImpl->m_pDrawingArea),
1180 : "expose-event",
1181 0 : G_CALLBACK(LOKDocView_Impl::renderOverlay), pDocView);
1182 : gtk_widget_add_events(pDocView->m_pImpl->m_pDrawingArea,
1183 : GDK_BUTTON_PRESS_MASK
1184 : |GDK_BUTTON_RELEASE_MASK
1185 0 : |GDK_BUTTON_MOTION_MASK);
1186 :
1187 0 : g_signal_connect(G_OBJECT(pDocView->m_pImpl->m_pDrawingArea),
1188 : "button-press-event",
1189 0 : G_CALLBACK(LOKDocView_Impl::signalButton), pDocView);
1190 0 : g_signal_connect(G_OBJECT(pDocView->m_pImpl->m_pDrawingArea),
1191 : "button-release-event",
1192 0 : G_CALLBACK(LOKDocView_Impl::signalButton), pDocView);
1193 0 : g_signal_connect(G_OBJECT(pDocView->m_pImpl->m_pDrawingArea),
1194 : "motion-notify-event",
1195 0 : G_CALLBACK(LOKDocView_Impl::signalMotion), pDocView);
1196 :
1197 0 : g_signal_connect(G_OBJECT(pDocView), "destroy", G_CALLBACK(LOKDocView_Impl::destroy), 0);
1198 0 : }
1199 :
1200 0 : SAL_DLLPUBLIC_EXPORT GtkWidget* lok_doc_view_new( LibreOfficeKit* pOffice )
1201 : {
1202 0 : LOKDocView* pDocView = LOK_DOC_VIEW(gtk_type_new(lok_doc_view_get_type()));
1203 0 : pDocView->m_pImpl->m_pOffice = pOffice;
1204 0 : return GTK_WIDGET( pDocView );
1205 : }
1206 :
1207 0 : SAL_DLLPUBLIC_EXPORT gboolean lok_doc_view_open_document( LOKDocView* pDocView, char* pPath )
1208 : {
1209 0 : if ( pDocView->m_pImpl->m_pDocument )
1210 : {
1211 0 : pDocView->m_pImpl->m_pDocument->pClass->destroy( pDocView->m_pImpl->m_pDocument );
1212 0 : pDocView->m_pImpl->m_pDocument = 0;
1213 : }
1214 :
1215 0 : pDocView->m_pImpl->m_pOffice->pClass->registerCallback(pDocView->m_pImpl->m_pOffice, &LOKDocView_Impl::globalCallbackWorker, pDocView);
1216 : pDocView->m_pImpl->m_pDocument = pDocView->m_pImpl->m_pOffice->pClass->documentLoad( pDocView->m_pImpl->m_pOffice,
1217 0 : pPath );
1218 0 : if ( !pDocView->m_pImpl->m_pDocument )
1219 : {
1220 : // FIXME: should have a GError parameter and populate it.
1221 0 : char *pError = pDocView->m_pImpl->m_pOffice->pClass->getError( pDocView->m_pImpl->m_pOffice );
1222 0 : fprintf( stderr, "Error opening document '%s'\n", pError );
1223 0 : return FALSE;
1224 : }
1225 : else
1226 : {
1227 0 : pDocView->m_pImpl->m_pDocument->pClass->initializeForRendering(pDocView->m_pImpl->m_pDocument);
1228 0 : pDocView->m_pImpl->m_pDocument->pClass->registerCallback(pDocView->m_pImpl->m_pDocument, &LOKDocView_Impl::callbackWorker, pDocView);
1229 0 : pDocView->m_pImpl->m_pDocument->pClass->getDocumentSize(pDocView->m_pImpl->m_pDocument, &pDocView->m_pImpl->m_nDocumentWidthTwips, &pDocView->m_pImpl->m_nDocumentHeightTwips);
1230 0 : g_timeout_add(600, &LOKDocView_Impl::handleTimeout, pDocView);
1231 :
1232 0 : float zoom = pDocView->m_pImpl->m_fZoom;
1233 0 : long nDocumentWidthTwips = pDocView->m_pImpl->m_nDocumentWidthTwips;
1234 0 : long nDocumentHeightTwips = pDocView->m_pImpl->m_nDocumentHeightTwips;
1235 0 : long nDocumentWidthPixels = twipToPixel(nDocumentWidthTwips, zoom);
1236 0 : long nDocumentHeightPixels = twipToPixel(nDocumentHeightTwips, zoom);
1237 : // Total number of columns in this document.
1238 0 : guint nColumns = ceil((double)nDocumentWidthPixels / nTileSizePixels);
1239 :
1240 :
1241 0 : pDocView->m_pImpl->m_aTileBuffer = TileBuffer(pDocView->m_pImpl->m_pDocument,
1242 0 : nColumns);
1243 : gtk_widget_set_size_request(pDocView->m_pImpl->m_pDrawingArea,
1244 : nDocumentWidthPixels,
1245 0 : nDocumentHeightPixels);
1246 0 : pDocView->m_pImpl->renderDocument(0);
1247 : }
1248 :
1249 0 : return TRUE;
1250 : }
1251 :
1252 0 : SAL_DLLPUBLIC_EXPORT LibreOfficeKitDocument* lok_doc_view_get_document(LOKDocView* pDocView)
1253 : {
1254 0 : return pDocView->m_pImpl->m_pDocument;
1255 : }
1256 :
1257 0 : SAL_DLLPUBLIC_EXPORT void lok_doc_view_set_zoom ( LOKDocView* pDocView, float fZoom )
1258 : {
1259 0 : pDocView->m_pImpl->m_fZoom = fZoom;
1260 0 : long nDocumentWidthPixels = twipToPixel(pDocView->m_pImpl->m_nDocumentWidthTwips, fZoom);
1261 0 : long nDocumentHeightPixels = twipToPixel(pDocView->m_pImpl->m_nDocumentHeightTwips, fZoom);
1262 : // Total number of columns in this document.
1263 0 : guint nColumns = ceil((double)nDocumentWidthPixels / nTileSizePixels);
1264 :
1265 0 : pDocView->m_pImpl->m_aTileBuffer = TileBuffer(pDocView->m_pImpl->m_pDocument,
1266 0 : nColumns);
1267 : gtk_widget_set_size_request(pDocView->m_pImpl->m_pDrawingArea,
1268 : nDocumentWidthPixels,
1269 0 : nDocumentHeightPixels);
1270 :
1271 0 : if ( pDocView->m_pImpl->m_pDocument )
1272 0 : pDocView->m_pImpl->renderDocument(0);
1273 0 : }
1274 :
1275 0 : SAL_DLLPUBLIC_EXPORT float lok_doc_view_get_zoom ( LOKDocView* pDocView )
1276 : {
1277 0 : return pDocView->m_pImpl->m_fZoom;
1278 : }
1279 :
1280 0 : SAL_DLLPUBLIC_EXPORT int lok_doc_view_get_parts( LOKDocView* pDocView )
1281 : {
1282 0 : return pDocView->m_pImpl->m_pDocument->pClass->getParts( pDocView->m_pImpl->m_pDocument );
1283 : }
1284 :
1285 0 : SAL_DLLPUBLIC_EXPORT int lok_doc_view_get_part( LOKDocView* pDocView )
1286 : {
1287 0 : return pDocView->m_pImpl->m_pDocument->pClass->getPart( pDocView->m_pImpl->m_pDocument );
1288 : }
1289 :
1290 0 : SAL_DLLPUBLIC_EXPORT void lok_doc_view_set_part( LOKDocView* pDocView, int nPart)
1291 : {
1292 0 : pDocView->m_pImpl->m_pDocument->pClass->setPart( pDocView->m_pImpl->m_pDocument, nPart );
1293 0 : }
1294 :
1295 0 : SAL_DLLPUBLIC_EXPORT char* lok_doc_view_get_part_name( LOKDocView* pDocView, int nPart )
1296 : {
1297 0 : return pDocView->m_pImpl->m_pDocument->pClass->getPartName( pDocView->m_pImpl->m_pDocument, nPart );
1298 : }
1299 :
1300 0 : SAL_DLLPUBLIC_EXPORT void lok_doc_view_set_partmode( LOKDocView* pDocView,
1301 : int nPartMode )
1302 : {
1303 0 : pDocView->m_pImpl->m_pDocument->pClass->setPartMode( pDocView->m_pImpl->m_pDocument, nPartMode );
1304 0 : pDocView->m_pImpl->renderDocument(0);
1305 0 : }
1306 :
1307 0 : SAL_DLLPUBLIC_EXPORT void lok_doc_view_set_edit( LOKDocView* pDocView,
1308 : gboolean bEdit )
1309 : {
1310 0 : gboolean bWasEdit = pDocView->m_pImpl->m_bEdit;
1311 :
1312 0 : if (!pDocView->m_pImpl->m_bEdit && bEdit)
1313 0 : g_info("lok_doc_view_set_edit: entering edit mode");
1314 0 : else if (pDocView->m_pImpl->m_bEdit && !bEdit)
1315 : {
1316 0 : g_info("lok_doc_view_set_edit: leaving edit mode");
1317 0 : pDocView->m_pImpl->m_pDocument->pClass->resetSelection(pDocView->m_pImpl->m_pDocument);
1318 : }
1319 0 : pDocView->m_pImpl->m_bEdit = bEdit;
1320 0 : g_signal_emit(pDocView, doc_view_signals[EDIT_CHANGED], 0, bWasEdit);
1321 0 : gtk_widget_queue_draw(GTK_WIDGET(pDocView->m_pImpl->m_pDrawingArea));
1322 0 : }
1323 :
1324 0 : SAL_DLLPUBLIC_EXPORT gboolean lok_doc_view_get_edit(LOKDocView* pDocView)
1325 : {
1326 0 : return pDocView->m_pImpl->m_bEdit;
1327 : }
1328 :
1329 0 : SAL_DLLPUBLIC_EXPORT void lok_doc_view_post_command(LOKDocView* pDocView, const char* pCommand, const char* pArguments)
1330 : {
1331 0 : pDocView->m_pImpl->m_pDocument->pClass->postUnoCommand(pDocView->m_pImpl->m_pDocument, pCommand, pArguments);
1332 0 : }
1333 :
1334 0 : SAL_DLLPUBLIC_EXPORT void lok_doc_view_post_key(GtkWidget* /*pWidget*/, GdkEventKey* pEvent, gpointer pData)
1335 : {
1336 0 : LOKDocView* pDocView = static_cast<LOKDocView *>(pData);
1337 0 : pDocView->m_pImpl->signalKey(pEvent);
1338 0 : }
1339 :
1340 0 : SAL_DLLPUBLIC_EXPORT void lok_doc_view_get_visarea(LOKDocView* pThis, GdkRectangle* pArea)
1341 : {
1342 : #if GTK_CHECK_VERSION(2,14,0) // we need gtk_adjustment_get_page_size()
1343 0 : float zoom = pThis->m_pImpl->m_fZoom;
1344 0 : GtkAdjustment* pHAdjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(pThis));
1345 0 : pArea->x = pixelToTwip(gtk_adjustment_get_value(pHAdjustment),zoom);
1346 0 : pArea->width = pixelToTwip(gtk_adjustment_get_page_size(pHAdjustment), zoom);
1347 0 : GtkAdjustment* pVAdjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(pThis));
1348 0 : pArea->y = pixelToTwip(gtk_adjustment_get_value(pVAdjustment), zoom);
1349 0 : pArea->height = pixelToTwip(gtk_adjustment_get_page_size(pVAdjustment), zoom);
1350 : #endif
1351 0 : }
1352 :
1353 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|