tdf#130857 qt weld: Implement QtInstanceWidget::get_text_height
[LibreOffice.git] / desktop / qa / desktop_lib / test_desktop_lib.cxx
blob11db3d75398bfb8aa5ae2beae94841c10fef508f
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 <config_oox.h>
11 #include <memory>
12 #include <string_view>
14 #include <com/sun/star/lang/XComponent.hpp>
15 #include <com/sun/star/text/XTextDocument.hpp>
16 #include <com/sun/star/awt/Key.hpp>
17 #include <com/sun/star/awt/XReschedule.hpp>
18 #include <com/sun/star/awt/Toolkit.hpp>
19 #include <com/sun/star/drawing/XDrawPageSupplier.hpp>
20 #include <com/sun/star/text/TextContentAnchorType.hpp>
21 #include <boost/property_tree/json_parser.hpp>
22 #include <com/sun/star/beans/XPropertySet.hpp>
23 #include <com/sun/star/util/XCloseable.hpp>
25 #include <vcl/scheduler.hxx>
26 #include <vcl/svapp.hxx>
27 #include <vcl/syswin.hxx>
28 #include <vcl/window.hxx>
29 #include <vcl/ctrl.hxx>
30 #include <vcl/uitest/uiobject.hxx>
31 #include <comphelper/processfactory.hxx>
32 #include <rtl/math.hxx>
33 #include <sfx2/childwin.hxx>
34 #include <sfx2/lokhelper.hxx>
35 #include <test/unoapi_test.hxx>
36 #include <comphelper/lok.hxx>
37 #include <comphelper/propertysequence.hxx>
38 #include <osl/conditn.hxx>
39 #include <svl/srchitem.hxx>
40 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
41 #include <unotools/tempfile.hxx>
42 #include <sfx2/viewsh.hxx>
43 #include <sfx2/viewfrm.hxx>
44 #include <sfx2/bindings.hxx>
45 #include <unotools/datetime.hxx>
46 #include <unotools/syslocaleoptions.hxx>
47 #include <comphelper/string.hxx>
48 #include <comphelper/scopeguard.hxx>
49 #include <cairo.h>
50 #include <config_fonts.h>
51 #include <config_mpl.h>
52 #include <tools/json_writer.hxx>
53 #include <o3tl/unit_conversion.hxx>
54 #include <o3tl/string_view.hxx>
56 #include <lib/init.hxx>
57 #include <svx/svxids.hrc>
59 #include <cppunit/TestAssert.h>
60 #include <vcl/BitmapTools.hxx>
61 #include <vcl/filter/PngImageWriter.hxx>
62 #include <vcl/filter/PDFiumLibrary.hxx>
63 #include <svtools/colorcfg.hxx>
64 #include <sal/types.h>
65 #include <test/lokcallback.hxx>
67 #if USE_TLS_NSS
68 #include <nss.h>
69 #endif
71 using namespace com::sun::star;
72 using namespace desktop;
74 static LibreOfficeKitDocumentType getDocumentTypeFromName(const char* pName)
76 CPPUNIT_ASSERT_MESSAGE("Document name must be valid.", pName != nullptr);
78 const std::string name(pName);
79 CPPUNIT_ASSERT_MESSAGE("Document name must include extension.", name.size() > 4);
81 const auto it = name.rfind('.');
82 if (it != std::string::npos)
84 const std::string ext = name.substr(it);
86 if (ext == ".ods")
87 return LOK_DOCTYPE_SPREADSHEET;
89 if (ext == ".odp")
90 return LOK_DOCTYPE_PRESENTATION;
93 CPPUNIT_ASSERT_MESSAGE("Document name must include extension.", it != std::string::npos);
94 return LOK_DOCTYPE_TEXT;
97 class DesktopLOKTest : public UnoApiTest
99 public:
100 DesktopLOKTest() : UnoApiTest(u"/desktop/qa/data/"_ustr),
101 m_nSelectionBeforeSearchResult(0),
102 m_nSelectionAfterSearchResult(0),
103 m_bModified(false),
104 m_nTrackChanges(0)
107 ~DesktopLOKTest();
109 void readFileIntoByteVector(
110 std::u16string_view sFilename, std::vector<sal_uInt8> & rByteVector);
112 virtual void setUp() override
114 comphelper::LibreOfficeKit::setActive(true);
116 UnoApiTest::setUp();
119 virtual void tearDown() override
121 closeDoc();
123 // documents are already closed, no need to call UnoApiTest::tearDown
124 test::BootstrapFixture::tearDown();
126 comphelper::LibreOfficeKit::setActive(false);
129 std::unique_ptr<LibLODocument_Impl>
130 loadDocImpl(const char* pName, LibreOfficeKitDocumentType eType);
132 private:
133 std::unique_ptr<LibLODocument_Impl>
134 loadDocImpl(const char* pName);
136 public:
137 std::unique_ptr<LibLODocument_Impl>
138 loadDocUrlImpl(const OUString& rFileURL, LibreOfficeKitDocumentType eType);
140 LibLODocument_Impl* loadDocUrl(const OUString& rFileURL, LibreOfficeKitDocumentType eType);
141 LibLODocument_Impl* loadDoc(const char* pName, LibreOfficeKitDocumentType eType);
142 LibLODocument_Impl* loadDoc(const char* pName)
144 return loadDoc(pName, getDocumentTypeFromName(pName));
147 void closeDoc(std::unique_ptr<LibLODocument_Impl>& loDocument);
148 void closeDoc() { closeDoc(m_pDocument); }
149 static void callback(int nType, const char* pPayload, void* pData);
150 void callbackImpl(int nType, const char* pPayload);
152 void testGetStyles();
153 void testGetFonts();
154 void testCreateView();
155 void testGetFilterTypes();
156 void testGetPartPageRectangles();
157 void testSearchCalc();
158 void testSearchAllNotificationsCalc();
159 void testPaintTile();
160 void testSaveAs();
161 void testSaveAsJsonOptions();
162 void testSaveAsCalc();
163 void testPasteWriter();
164 void testPasteWriterJPEG();
165 void testUndoWriter();
166 void testRowColumnHeaders();
167 void testHiddenRowHeaders();
168 void testCellCursor();
169 void testCommandResult();
170 void testWriterComments();
171 void testSheetOperations();
172 void testSheetSelections();
173 void testSheetDragDrop();
174 void testContextMenuCalc();
175 void testContextMenuWriter();
176 void testContextMenuImpress();
177 void testNotificationCompression();
178 void testTileInvalidationCompression();
179 void testPartInInvalidation();
180 void testBinaryCallback();
181 void testInput();
182 void testRedlineWriter();
183 void testTrackChanges();
184 void testRedlineCalc();
185 void testPaintPartTile();
186 void testPaintPartTileDifferentSchemes();
187 #if HAVE_MORE_FONTS
188 void testGetFontSubset();
189 #endif
190 void testCommentsWriter();
191 void testCommentsCalc();
192 void testCommentsImpress();
193 void testCommentsCallbacksWriter();
194 void testCommentsAddEditDeleteDraw();
195 void testCommentsInReadOnlyMode();
196 void testRunMacro();
197 void testExtractParameter();
198 void testGetSignatureState_NonSigned();
199 void testGetSignatureState_Signed();
200 #if 0 // broken with system nss on RHEL 7
201 void testInsertCertificate_DER_ODT();
202 void testInsertCertificate_PEM_ODT();
203 void testInsertCertificate_PEM_DOCX();
204 #endif
205 void testSignDocument_PEM_PDF();
206 void testTextSelectionHandles();
207 void testComplexSelection();
208 void testSpellcheckerMultiView();
209 void testDialogPaste();
210 void testCalcSaveAs();
211 void testControlState();
212 void testMetricField();
213 void testMultiDocuments();
214 void testJumpCursor();
215 void testRenderSearchResult_WriterNode();
216 void testRenderSearchResult_CommonNode();
217 void testNoDuplicateTableSelection();
218 void testMultiViewTableSelection();
219 void testColorPaletteCallback();
220 void testABI();
222 CPPUNIT_TEST_SUITE(DesktopLOKTest);
223 CPPUNIT_TEST(testGetStyles);
224 CPPUNIT_TEST(testGetFonts);
225 CPPUNIT_TEST(testCreateView);
226 CPPUNIT_TEST(testGetFilterTypes);
227 CPPUNIT_TEST(testGetPartPageRectangles);
228 CPPUNIT_TEST(testSearchCalc);
229 CPPUNIT_TEST(testSearchAllNotificationsCalc);
230 CPPUNIT_TEST(testPaintTile);
231 CPPUNIT_TEST(testSaveAs);
232 CPPUNIT_TEST(testSaveAsJsonOptions);
233 CPPUNIT_TEST(testSaveAsCalc);
234 CPPUNIT_TEST(testPasteWriter);
235 CPPUNIT_TEST(testPasteWriterJPEG);
236 CPPUNIT_TEST(testUndoWriter);
237 CPPUNIT_TEST(testRowColumnHeaders);
238 CPPUNIT_TEST(testHiddenRowHeaders);
239 CPPUNIT_TEST(testCellCursor);
240 CPPUNIT_TEST(testCommandResult);
241 CPPUNIT_TEST(testWriterComments);
242 CPPUNIT_TEST(testSheetOperations);
243 CPPUNIT_TEST(testSheetSelections);
244 CPPUNIT_TEST(testSheetDragDrop);
245 CPPUNIT_TEST(testContextMenuCalc);
246 CPPUNIT_TEST(testContextMenuWriter);
247 CPPUNIT_TEST(testContextMenuImpress);
248 CPPUNIT_TEST(testNotificationCompression);
249 CPPUNIT_TEST(testTileInvalidationCompression);
250 CPPUNIT_TEST(testPartInInvalidation);
251 CPPUNIT_TEST(testBinaryCallback);
252 CPPUNIT_TEST(testInput);
253 CPPUNIT_TEST(testRedlineWriter);
254 CPPUNIT_TEST(testTrackChanges);
255 CPPUNIT_TEST(testRedlineCalc);
256 CPPUNIT_TEST(testPaintPartTile);
257 CPPUNIT_TEST(testPaintPartTileDifferentSchemes);
258 #if HAVE_MORE_FONTS
259 CPPUNIT_TEST(testGetFontSubset);
260 #endif
261 CPPUNIT_TEST(testCommentsWriter);
262 CPPUNIT_TEST(testCommentsCalc);
263 CPPUNIT_TEST(testCommentsImpress);
264 CPPUNIT_TEST(testCommentsCallbacksWriter);
265 CPPUNIT_TEST(testCommentsAddEditDeleteDraw);
266 CPPUNIT_TEST(testCommentsInReadOnlyMode);
267 CPPUNIT_TEST(testRunMacro);
268 CPPUNIT_TEST(testExtractParameter);
269 CPPUNIT_TEST(testGetSignatureState_Signed);
270 CPPUNIT_TEST(testGetSignatureState_NonSigned);
271 #if !MPL_HAVE_SUBSET
272 #if 0 // broken with system nss on RHEL 7
273 CPPUNIT_TEST(testInsertCertificate_DER_ODT);
274 CPPUNIT_TEST(testInsertCertificate_PEM_ODT);
275 CPPUNIT_TEST(testInsertCertificate_PEM_DOCX);
276 #endif
277 CPPUNIT_TEST(testSignDocument_PEM_PDF);
278 #endif
279 CPPUNIT_TEST(testTextSelectionHandles);
280 CPPUNIT_TEST(testComplexSelection);
281 CPPUNIT_TEST(testSpellcheckerMultiView);
282 CPPUNIT_TEST(testDialogPaste);
283 CPPUNIT_TEST(testCalcSaveAs);
284 CPPUNIT_TEST(testControlState);
285 CPPUNIT_TEST(testMetricField);
286 CPPUNIT_TEST(testMultiDocuments);
287 CPPUNIT_TEST(testJumpCursor);
288 CPPUNIT_TEST(testRenderSearchResult_WriterNode);
289 CPPUNIT_TEST(testRenderSearchResult_CommonNode);
290 CPPUNIT_TEST(testNoDuplicateTableSelection);
291 CPPUNIT_TEST(testMultiViewTableSelection);
292 CPPUNIT_TEST(testColorPaletteCallback);
293 CPPUNIT_TEST(testABI);
294 CPPUNIT_TEST_SUITE_END();
296 OString m_aTextSelection;
297 OString m_aTextSelectionStart;
298 OString m_aTextSelectionEnd;
299 std::vector<OString> m_aSearchResultSelection;
300 std::vector<int> m_aSearchResultPart;
301 int m_nSelectionBeforeSearchResult;
302 int m_nSelectionAfterSearchResult;
304 // for testCommandResult
305 osl::Condition m_aCommandResultCondition;
306 OString m_aCommandResult;
308 // for testModifiedStatus
309 osl::Condition m_aStateChangedCondition;
310 bool m_bModified;
311 int m_nTrackChanges;
313 // for testContextMenu{Calc, Writer}
314 osl::Condition m_aContextMenuCondition;
315 boost::property_tree::ptree m_aContextMenuResult;
317 std::unique_ptr<LibLODocument_Impl> m_pDocument;
320 DesktopLOKTest::~DesktopLOKTest()
322 #if USE_TLS_NSS
323 NSS_Shutdown();
324 #endif
327 static Control* GetFocusControl(vcl::Window const * pParent)
329 sal_uInt16 nChildren = pParent->GetChildCount();
330 for (sal_uInt16 nChild = 0; nChild < nChildren; ++nChild)
332 vcl::Window* pChild = pParent->GetChild( nChild );
333 Control* pCtrl = dynamic_cast<Control*>(pChild);
334 if (pCtrl && pCtrl->HasControlFocus())
335 return pCtrl;
337 Control* pSubCtrl = GetFocusControl( pChild );
338 if (pSubCtrl)
339 return pSubCtrl;
341 return nullptr;
344 std::unique_ptr<LibLODocument_Impl>
345 DesktopLOKTest::loadDocUrlImpl(const OUString& rFileURL, LibreOfficeKitDocumentType eType)
347 OUString aService;
348 switch (eType)
350 case LOK_DOCTYPE_TEXT:
351 aService = "com.sun.star.text.TextDocument";
352 break;
353 case LOK_DOCTYPE_SPREADSHEET:
354 aService = "com.sun.star.sheet.SpreadsheetDocument";
355 break;
356 case LOK_DOCTYPE_PRESENTATION:
357 aService = "com.sun.star.presentation.PresentationDocument";
358 break;
359 default:
360 CPPUNIT_ASSERT(false);
361 break;
364 static int nDocumentIdCounter = 0;
365 SfxViewShell::SetCurrentDocId(ViewShellDocId(nDocumentIdCounter));
366 mxComponent = loadFromDesktop(rFileURL, aService);
368 std::unique_ptr<LibLODocument_Impl> pDocument(new LibLODocument_Impl(mxComponent, nDocumentIdCounter));
369 ++nDocumentIdCounter;
371 return pDocument;
374 std::unique_ptr<LibLODocument_Impl>
375 DesktopLOKTest::loadDocImpl(const char* pName, LibreOfficeKitDocumentType eType)
377 OUString aFileURL = createFileURL(OUString::createFromAscii(pName));
378 return loadDocUrlImpl(aFileURL, eType);
381 std::unique_ptr<LibLODocument_Impl>
382 DesktopLOKTest::loadDocImpl(const char* pName)
384 return loadDocImpl(pName, getDocumentTypeFromName(pName));
387 LibLODocument_Impl* DesktopLOKTest::loadDocUrl(const OUString& rFileURL, LibreOfficeKitDocumentType eType)
389 m_pDocument = loadDocUrlImpl(rFileURL, eType);
390 return m_pDocument.get();
393 LibLODocument_Impl* DesktopLOKTest::loadDoc(const char* pName, LibreOfficeKitDocumentType eType)
395 m_pDocument = loadDocImpl(pName, eType);
396 return m_pDocument.get();
399 void DesktopLOKTest::closeDoc(std::unique_ptr<LibLODocument_Impl>& pDocument)
401 if (pDocument)
403 pDocument->pClass->registerCallback(pDocument.get(), nullptr, nullptr);
404 pDocument.reset();
407 if (mxComponent.is())
409 css::uno::Reference<util::XCloseable> xCloseable(mxComponent, css::uno::UNO_QUERY_THROW);
410 xCloseable->close(false);
411 mxComponent.clear();
415 void DesktopLOKTest::callback(int nType, const char* pPayload, void* pData)
417 static_cast<DesktopLOKTest*>(pData)->callbackImpl(nType, pPayload);
420 void DesktopLOKTest::callbackImpl(int nType, const char* pPayload)
422 switch (nType)
424 case LOK_CALLBACK_TEXT_SELECTION:
426 m_aTextSelection = pPayload;
427 if (m_aSearchResultSelection.empty())
428 ++m_nSelectionBeforeSearchResult;
429 else
430 ++m_nSelectionAfterSearchResult;
432 break;
433 case LOK_CALLBACK_TEXT_SELECTION_START:
434 m_aTextSelectionStart = pPayload;
435 break;
436 case LOK_CALLBACK_TEXT_SELECTION_END:
437 m_aTextSelectionEnd = pPayload;
438 break;
439 case LOK_CALLBACK_SEARCH_RESULT_SELECTION:
441 m_aSearchResultSelection.clear();
442 boost::property_tree::ptree aTree;
443 std::stringstream aStream(pPayload);
444 boost::property_tree::read_json(aStream, aTree);
445 for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("searchResultSelection"))
447 m_aSearchResultSelection.emplace_back(rValue.second.get<std::string>("rectangles").c_str());
448 m_aSearchResultPart.push_back(std::atoi(rValue.second.get<std::string>("part").c_str()));
451 break;
452 case LOK_CALLBACK_UNO_COMMAND_RESULT:
454 m_aCommandResult = pPayload;
455 m_aCommandResultCondition.set();
457 break;
458 case LOK_CALLBACK_STATE_CHANGED:
460 OString aPayload(pPayload);
461 OString aPrefix(".uno:ModifiedStatus="_ostr);
462 if (aPayload.startsWith(aPrefix))
464 m_bModified = aPayload.copy(aPrefix.getLength()).toBoolean();
465 m_aStateChangedCondition.set();
467 else if (aPayload.startsWith(".uno:TrackChanges=") && aPayload.endsWith("=true"))
468 ++m_nTrackChanges;
470 break;
471 case LOK_CALLBACK_CONTEXT_MENU:
473 m_aContextMenuResult.clear();
474 std::stringstream aStream(pPayload);
475 boost::property_tree::read_json(aStream, m_aContextMenuResult);
476 m_aContextMenuCondition.set();
478 break;
482 void DesktopLOKTest::testGetStyles()
484 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
485 boost::property_tree::ptree aTree;
486 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:StyleApply");
487 std::stringstream aStream(pJSON);
488 boost::property_tree::read_json(aStream, aTree);
489 CPPUNIT_ASSERT( !aTree.empty() );
490 CPPUNIT_ASSERT_EQUAL( std::string(".uno:StyleApply"), aTree.get_child("commandName").get_value<std::string>() );
492 boost::property_tree::ptree aValues = aTree.get_child("commandValues");
493 CPPUNIT_ASSERT( !aValues.empty() );
494 for (const auto& rPair : aValues)
496 if( rPair.first != "ClearStyle")
498 CPPUNIT_ASSERT( !rPair.second.empty());
500 if (rPair.first != "CharacterStyles" &&
501 rPair.first != "ParagraphStyles" &&
502 rPair.first != "FrameStyles" &&
503 rPair.first != "PageStyles" &&
504 rPair.first != "NumberingStyles" &&
505 rPair.first != "CellStyles" &&
506 rPair.first != "ShapeStyles" &&
507 rPair.first != "TableStyles" &&
508 rPair.first != "HeaderFooter" &&
509 rPair.first != "Commands")
511 CPPUNIT_FAIL("Unknown style family: " + rPair.first);
514 free(pJSON);
517 void DesktopLOKTest::testGetFonts()
519 LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp");
520 boost::property_tree::ptree aTree;
521 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:CharFontName");
522 std::stringstream aStream(pJSON);
523 boost::property_tree::read_json(aStream, aTree);
524 CPPUNIT_ASSERT( !aTree.empty() );
525 CPPUNIT_ASSERT_EQUAL( std::string(".uno:CharFontName"), aTree.get_child("commandName").get_value<std::string>() );
527 boost::property_tree::ptree aValues = aTree.get_child("commandValues");
528 CPPUNIT_ASSERT( !aValues.empty() );
529 for (const auto& rPair : aValues)
531 // check that we have font sizes available for each font
532 CPPUNIT_ASSERT( !rPair.second.empty());
534 free(pJSON);
537 void DesktopLOKTest::testCreateView()
539 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
540 CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument));
542 int nId0 = pDocument->m_pDocumentClass->getView(pDocument);
543 int nId1 = pDocument->m_pDocumentClass->createView(pDocument);
544 CPPUNIT_ASSERT_EQUAL(2, pDocument->m_pDocumentClass->getViewsCount(pDocument));
546 // Test getViewIds().
547 std::vector<int> aViewIds(2);
548 CPPUNIT_ASSERT(pDocument->m_pDocumentClass->getViewIds(pDocument, aViewIds.data(), aViewIds.size()));
549 CPPUNIT_ASSERT_EQUAL(nId0, aViewIds[0]);
550 CPPUNIT_ASSERT_EQUAL(nId1, aViewIds[1]);
552 // Make sure the created view is the active one, then switch to the old
553 // one.
554 CPPUNIT_ASSERT_EQUAL(nId1, pDocument->m_pDocumentClass->getView(pDocument));
555 pDocument->m_pDocumentClass->setView(pDocument, nId0);
556 CPPUNIT_ASSERT_EQUAL(nId0, pDocument->m_pDocumentClass->getView(pDocument));
558 pDocument->m_pDocumentClass->destroyView(pDocument, nId1);
559 CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument));
562 void DesktopLOKTest::testGetPartPageRectangles()
564 // Test that we get as many page rectangles as expected: blank document is
565 // one page.
566 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
567 char* pRectangles = pDocument->pClass->getPartPageRectangles(pDocument);
568 OUString sRectangles = OUString::fromUtf8(pRectangles);
570 std::vector<OUString> aRectangles;
571 sal_Int32 nIndex = 0;
574 OUString aRectangle = sRectangles.getToken(0, ';', nIndex);
575 if (!aRectangle.isEmpty())
576 aRectangles.push_back(aRectangle);
578 while (nIndex >= 0);
579 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aRectangles.size());
581 free(pRectangles);
584 void DesktopLOKTest::testGetFilterTypes()
586 LibLibreOffice_Impl aOffice;
587 char* pJSON = aOffice.m_pOfficeClass->getFilterTypes(&aOffice);
589 std::stringstream aStream(pJSON);
590 boost::property_tree::ptree aTree;
591 boost::property_tree::read_json(aStream, aTree);
593 CPPUNIT_ASSERT(!aTree.empty());
594 CPPUNIT_ASSERT_EQUAL(std::string("application/vnd.oasis.opendocument.text"), aTree.get_child("writer8").get_child("MediaType").get_value<std::string>());
595 free(pJSON);
598 void DesktopLOKTest::testSearchCalc()
600 LibLibreOffice_Impl aOffice;
601 LibLODocument_Impl* pDocument = loadDoc("search.ods");
602 pDocument->pClass->initializeForRendering(pDocument, nullptr);
603 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
605 uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
607 {"SearchItem.SearchString", uno::Any(u"foo"_ustr)},
608 {"SearchItem.Backward", uno::Any(false)},
609 {"SearchItem.Command", uno::Any(static_cast<sal_uInt16>(SvxSearchCmd::FIND_ALL))},
610 }));
611 dispatchCommand(mxComponent, u".uno:ExecuteSearch"_ustr, aPropertyValues);
613 std::vector<OString> aSelections;
614 sal_Int32 nIndex = 0;
617 OString aToken = m_aTextSelection.getToken(0, ';', nIndex);
618 aSelections.push_back(aToken);
619 } while (nIndex >= 0);
620 // This was 1, find-all only found one match.
621 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aSelections.size());
622 // Make sure that we get exactly as many rectangle lists as matches.
623 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), m_aSearchResultSelection.size());
624 // Result is on the first sheet.
625 CPPUNIT_ASSERT_EQUAL(0, m_aSearchResultPart[0]);
628 void DesktopLOKTest::testSearchAllNotificationsCalc()
630 LibLibreOffice_Impl aOffice;
631 LibLODocument_Impl* pDocument = loadDoc("search.ods");
632 pDocument->pClass->initializeForRendering(pDocument, nullptr);
633 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
635 uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
637 {"SearchItem.SearchString", uno::Any(u"foo"_ustr)},
638 {"SearchItem.Backward", uno::Any(false)},
639 {"SearchItem.Command", uno::Any(static_cast<sal_uInt16>(SvxSearchCmd::FIND_ALL))},
640 }));
641 dispatchCommand(mxComponent, u".uno:ExecuteSearch"_ustr, aPropertyValues);
643 // This was 1, make sure that we get no notifications about selection changes during search.
644 CPPUNIT_ASSERT_EQUAL(0, m_nSelectionBeforeSearchResult);
645 // But we do get the selection afterwards.
646 CPPUNIT_ASSERT(m_nSelectionAfterSearchResult > 0);
649 void DesktopLOKTest::testPaintTile()
651 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
652 int nCanvasWidth = 100;
653 int nCanvasHeight = 300;
654 sal_Int32 nStride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, nCanvasWidth);
655 std::vector<unsigned char> aBuffer(nStride * nCanvasHeight);
656 int nTilePosX = 0;
657 int nTilePosY = 0;
658 int nTileWidth = 1000;
659 int nTileHeight = 3000;
661 // This used to crash: paintTile() implementation did not handle
662 // nCanvasWidth != nCanvasHeight correctly, as usually both are just always
663 // 256.
664 pDocument->pClass->paintTile(pDocument, aBuffer.data(), nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight);
666 // This crashed in OutputDevice::DrawDeviceAlphaBitmap().
667 nCanvasWidth = 200;
668 nCanvasHeight = 200;
669 nTileWidth = 4000;
670 nTileHeight = 4000;
671 aBuffer.resize(nCanvasWidth * nCanvasHeight * 4);
672 pDocument->pClass->paintTile(pDocument, aBuffer.data(), nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight);
675 void DesktopLOKTest::testSaveAs()
677 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
678 CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "png", nullptr));
681 void DesktopLOKTest::testSaveAsJsonOptions()
683 // Given a document with 3 pages:
684 LibLODocument_Impl* pDocument = loadDoc("3page.odg");
686 // When exporting that document to PDF, skipping the first page:
687 OString aOptions("{\"PageRange\":{\"type\":\"string\",\"value\":\"2-\"}}"_ostr);
688 CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "pdf", aOptions.getStr()));
690 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
691 if (!pPDFium)
692 return;
694 // Then make sure the resulting PDF has 2 pages:
695 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
696 = parsePDFExport();
697 // Without the accompanying fix in place, this test would have failed with:
698 // - Expected: 2
699 // - Actual : 3
700 // i.e. FilterOptions was ignored.
701 CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
704 void DesktopLOKTest::testSaveAsCalc()
706 LibLODocument_Impl* pDocument = loadDoc("search.ods");
707 CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "png", nullptr));
710 void DesktopLOKTest::testPasteWriter()
712 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
713 OString aText("hello"_ostr);
715 CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8", aText.getStr(), aText.getLength()));
717 pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false);
718 Scheduler::ProcessEventsToIdle();
719 char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr);
720 CPPUNIT_ASSERT_EQUAL("hello"_ostr, OString(pText));
721 free(pText);
723 // textt/plain should be rejected.
724 CPPUNIT_ASSERT(!pDocument->pClass->paste(pDocument, "textt/plain;charset=utf-8", aText.getStr(), aText.getLength()));
725 // Writer is expected to support text/html.
726 CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/html", aText.getStr(), aText.getLength()));
728 // Overwrite doc contents with a HTML paste.
729 pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false);
730 Scheduler::ProcessEventsToIdle();
731 OString aComment("foo <!-- bar --> baz"_ostr);
732 CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/html", aComment.getStr(), aComment.getLength()));
734 // Check if we have a comment.
735 uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
736 uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xTextDocument->getText(), uno::UNO_QUERY);
737 uno::Reference<container::XEnumeration> xParagraphEnumeration = xParagraphEnumerationAccess->createEnumeration();
738 uno::Reference<container::XEnumerationAccess> xParagraph(xParagraphEnumeration->nextElement(), uno::UNO_QUERY);
739 uno::Reference<container::XEnumeration> xTextPortionEnumeration = xParagraph->createEnumeration();
740 uno::Reference<beans::XPropertySet> xTextPortion(xTextPortionEnumeration->nextElement(), uno::UNO_QUERY);
741 CPPUNIT_ASSERT_EQUAL(u"Text"_ustr, xTextPortion->getPropertyValue(u"TextPortionType"_ustr).get<OUString>());
742 // Without the accompanying fix in place, this test would have failed, as we had a comment
743 // between "foo" and "baz".
744 CPPUNIT_ASSERT(!xTextPortionEnumeration->hasMoreElements());
747 void DesktopLOKTest::testPasteWriterJPEG()
749 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
751 OUString aFileURL = createFileURL(u"paste.jpg");
752 std::ifstream aImageStream(aFileURL.toUtf8().copy(strlen("file://")).getStr());
753 std::vector<char> aImageContents((std::istreambuf_iterator<char>(aImageStream)), std::istreambuf_iterator<char>());
755 CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "image/jpeg", aImageContents.data(), aImageContents.size()));
757 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
758 uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
759 // This was 0, JPEG was not handled as a format for clipboard paste.
760 CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xDrawPage->getCount());
762 uno::Reference<beans::XPropertySet> xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY);
763 // This was text::TextContentAnchorType_AT_PARAGRAPH.
764 CPPUNIT_ASSERT_EQUAL(text::TextContentAnchorType_AS_CHARACTER, xShape->getPropertyValue(u"AnchorType"_ustr).get<text::TextContentAnchorType>());
766 // Delete the pasted picture, and paste again with a custom anchor type.
767 uno::Reference<lang::XComponent>(xShape, uno::UNO_QUERY_THROW)->dispose();
768 uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
770 {"AnchorType", uno::Any(static_cast<sal_uInt16>(text::TextContentAnchorType_AT_CHARACTER))},
771 }));
772 dispatchCommand(mxComponent, u".uno:Paste"_ustr, aPropertyValues);
773 xShape.set(xDrawPage->getByIndex(0), uno::UNO_QUERY);
774 // This was text::TextContentAnchorType_AS_CHARACTER, AnchorType argument was ignored.
775 CPPUNIT_ASSERT_EQUAL(text::TextContentAnchorType_AT_CHARACTER, xShape->getPropertyValue(u"AnchorType"_ustr).get<text::TextContentAnchorType>());
778 void DesktopLOKTest::testUndoWriter()
780 // Load a Writer document and press a key.
781 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
782 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0);
783 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't', 0);
784 Scheduler::ProcessEventsToIdle();
785 // Get undo info.
786 boost::property_tree::ptree aTree;
787 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:Undo");
788 std::stringstream aStream(pJSON);
789 free(pJSON);
790 CPPUNIT_ASSERT(!aStream.str().empty());
791 boost::property_tree::read_json(aStream, aTree);
792 // Make sure that pressing a key creates exactly one undo action.
793 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aTree.get_child("actions").size());
796 void DesktopLOKTest::testRowColumnHeaders()
799 * Payload example:
802 * "rows": [
804 * "size": "254.987250637468",
805 * "text": "1"
806 * },
808 * "size": "509.974501274936",
809 * "text": "2"
811 * ],
812 * "columns": [
814 * "size": "1274.93625318734",
815 * "text": "A"
816 * },
818 * "size": "2549.87250637468",
819 * "text": "B"
824 * "size" defines the bottom/right boundary of a row/column in twips (size between 0 and boundary)
825 * "text" has the header label in UTF-8
827 LibLODocument_Impl* pDocument = loadDoc("search.ods");
829 pDocument->pClass->initializeForRendering(pDocument, nullptr);
831 tools::Long nWidth = 0;
832 tools::Long nHeight = 0;
833 pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight);
834 tools::Long nX = rtl::math::round(nWidth / 4.0);
835 tools::Long nY = rtl::math::round(nHeight / 4.0);
836 nWidth = rtl::math::round(nWidth / 2.0);
837 nHeight = rtl::math::round(nHeight / 2.0);
839 std::stringstream aPayload;
840 aPayload << ".uno:ViewRowColumnHeaders?x=" << nX << "&y=" << nY << "&width=" << nWidth << "&height=" << nHeight;
842 boost::property_tree::ptree aTree;
843 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aPayload.str().c_str());
844 std::stringstream aStream(pJSON);
845 free(pJSON);
847 CPPUNIT_ASSERT(!aStream.str().empty());
849 boost::property_tree::read_json(aStream, aTree);
850 sal_Int32 nPrevious = 0;
851 bool bFirstHeader = true;
852 bool bNotEnoughHeaders = true;
853 for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("rows"))
855 sal_Int32 nSize = o3tl::toInt32(rValue.second.get<std::string>("size"));
856 nSize = o3tl::convert(nSize, o3tl::Length::px, o3tl::Length::twip);
857 OString aText(rValue.second.get<std::string>("text"));
859 if (bFirstHeader)
861 CPPUNIT_ASSERT(nSize <= nY);
862 CPPUNIT_ASSERT_EQUAL("10"_ostr, aText);
863 bFirstHeader = false;
865 else
867 CPPUNIT_ASSERT(nSize > 0);
868 CPPUNIT_ASSERT(nPrevious < nSize);
869 if (nSize > nY + nHeight)
871 bNotEnoughHeaders = false;
872 break;
875 nPrevious = nSize;
877 CPPUNIT_ASSERT(!bNotEnoughHeaders);
879 nPrevious = 0;
880 bFirstHeader = true;
881 bNotEnoughHeaders = true;
882 for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("columns"))
884 sal_Int32 nSize = o3tl::toInt32(rValue.second.get<std::string>("size"));
885 nSize = o3tl::convert(nSize, o3tl::Length::px, o3tl::Length::twip);
886 OString aText(rValue.second.get<std::string>("text"));
887 if (bFirstHeader)
889 CPPUNIT_ASSERT(nSize <= nX);
890 CPPUNIT_ASSERT_EQUAL("3"_ostr, aText);
891 bFirstHeader = false;
893 else
895 CPPUNIT_ASSERT(nSize > 0);
896 CPPUNIT_ASSERT(nPrevious < nSize);
897 if (nSize > nX + nWidth)
899 bNotEnoughHeaders = false;
900 break;
903 nPrevious = nSize;
905 CPPUNIT_ASSERT(!bNotEnoughHeaders);
908 void DesktopLOKTest::testHiddenRowHeaders()
910 LibLODocument_Impl* pDocument = loadDoc("hidden-row.ods");
912 pDocument->pClass->initializeForRendering(pDocument, nullptr);
914 tools::Long const nX = 0;
915 tools::Long const nY = 0;
916 tools::Long nWidth = 0;
917 tools::Long nHeight = 0;
918 pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight);
920 std::stringstream aPayload;
921 aPayload << ".uno:ViewRowColumnHeaders?x=" << nX << "&y=" << nY << "&width=" << nWidth << "&height=" << nHeight;
923 boost::property_tree::ptree aTree;
924 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aPayload.str().c_str());
925 std::stringstream aStream(pJSON);
926 CPPUNIT_ASSERT(!aStream.str().empty());
928 boost::property_tree::read_json(aStream, aTree);
929 free(pJSON);
930 sal_Int32 nPrevious = 0;
931 sal_Int32 nIndex = 0;
932 for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("rows"))
934 sal_Int32 nSize = o3tl::toInt32(rValue.second.get<std::string>("size"));
936 if (nIndex++ == 2)
938 // nSize was 510, nPrevious was 255, i.e. hidden row wasn't reported as 0 height.
939 CPPUNIT_ASSERT_EQUAL(nPrevious, nSize);
940 break;
942 nPrevious = nSize;
946 void DesktopLOKTest::testCellCursor()
948 LibLODocument_Impl* pDocument = loadDoc("search.ods");
950 boost::property_tree::ptree aTree;
952 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:CellCursor?tileWidth=1&tileHeight=1&outputWidth=1&outputHeight=1");
954 std::stringstream aStream(pJSON);
955 free(pJSON);
956 CPPUNIT_ASSERT(!aStream.str().empty());
958 boost::property_tree::read_json(aStream, aTree);
960 OString aRectangle(aTree.get<std::string>("commandValues"));
961 // cell cursor geometry + col + row
962 CPPUNIT_ASSERT_EQUAL("0, 0, 1274, 254, 0, 0"_ostr, aRectangle);
965 void DesktopLOKTest::testCommandResult()
967 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
969 // the postUnoCommand() is supposed to be async, let's test it safely
970 // [no idea if it is async in reality - most probably we are operating
971 // under some solar mutex or something anyway ;-) - but...]
972 TimeValue aTimeValue = { 2 , 0 }; // 2 seconds max
974 // nothing is triggered when we have no callback yet, we just time out on
975 // the condition var.
976 m_aCommandResultCondition.reset();
977 pDocument->pClass->postUnoCommand(pDocument, ".uno:Bold", nullptr, true);
978 Scheduler::ProcessEventsToIdle();
979 m_aCommandResultCondition.wait(aTimeValue);
981 CPPUNIT_ASSERT(m_aCommandResult.isEmpty());
983 // but we get some real values when the callback is set up
984 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
986 m_aCommandResultCondition.reset();
987 pDocument->pClass->postUnoCommand(pDocument, ".uno:Bold", nullptr, true);
988 Scheduler::ProcessEventsToIdle();
989 m_aCommandResultCondition.wait(aTimeValue);
991 boost::property_tree::ptree aTree;
992 std::stringstream aStream((std::string(m_aCommandResult)));
993 boost::property_tree::read_json(aStream, aTree);
995 CPPUNIT_ASSERT_EQUAL(std::string(".uno:Bold"), aTree.get_child("commandName").get_value<std::string>());
996 CPPUNIT_ASSERT_EQUAL(true, aTree.get_child("success").get_value<bool>());
999 void DesktopLOKTest::testWriterComments()
1001 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1002 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
1003 uno::Reference<awt::XReschedule> xToolkit = css::awt::Toolkit::create(comphelper::getProcessComponentContext());
1005 // Insert a comment at the beginning of the document and wait till the main
1006 // loop grabs the focus, so characters end up in the annotation window.
1007 TimeValue const aTimeValue = {2 , 0}; // 2 seconds max
1008 m_aCommandResultCondition.reset();
1009 pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", nullptr, true);
1010 Scheduler::ProcessEventsToIdle();
1011 m_aCommandResultCondition.wait(aTimeValue);
1012 CPPUNIT_ASSERT(!m_aCommandResult.isEmpty());
1013 xToolkit->reschedule();
1015 // Test that we have a comment.
1016 uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
1017 uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xTextDocument->getText(), uno::UNO_QUERY);
1018 uno::Reference<container::XEnumeration> xParagraphEnumeration = xParagraphEnumerationAccess->createEnumeration();
1019 uno::Reference<container::XEnumerationAccess> xParagraph(xParagraphEnumeration->nextElement(), uno::UNO_QUERY);
1020 uno::Reference<container::XEnumeration> xTextPortionEnumeration = xParagraph->createEnumeration();
1021 uno::Reference<beans::XPropertySet> xTextPortion(xTextPortionEnumeration->nextElement(), uno::UNO_QUERY);
1022 CPPUNIT_ASSERT_EQUAL(u"Annotation"_ustr, xTextPortion->getPropertyValue(u"TextPortionType"_ustr).get<OUString>());
1024 // Type "test" and finish editing.
1025 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0);
1026 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'e', 0);
1027 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 's', 0);
1028 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0);
1029 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::ESCAPE);
1030 Scheduler::ProcessEventsToIdle();
1032 // Test that the typed characters ended up in the right window.
1033 auto xTextField = xTextPortion->getPropertyValue(u"TextField"_ustr).get< uno::Reference<beans::XPropertySet> >();
1034 // This was empty, typed characters ended up in the body text.
1035 CPPUNIT_ASSERT_EQUAL(u"test"_ustr, xTextField->getPropertyValue(u"Content"_ustr).get<OUString>());
1038 void DesktopLOKTest::testTrackChanges()
1040 // Load a document and create two views.
1041 LibLibreOffice_Impl aOffice;
1042 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1043 pDocument->pClass->initializeForRendering(pDocument, nullptr);
1044 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
1045 pDocument->pClass->createView(pDocument);
1046 pDocument->pClass->initializeForRendering(pDocument, nullptr);
1047 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
1048 Scheduler::ProcessEventsToIdle();
1050 // Enable track changes and assert that both views get notified.
1051 m_nTrackChanges = 0;
1052 pDocument->pClass->postUnoCommand(pDocument, ".uno:TrackChanges", nullptr, false);
1053 Scheduler::ProcessEventsToIdle();
1054 // This was 1, only the active view was notified.
1055 CPPUNIT_ASSERT_EQUAL(2, m_nTrackChanges);
1058 void DesktopLOKTest::testSheetOperations()
1060 LibLODocument_Impl* pDocument = loadDoc("sheets.ods");
1062 // insert the last sheet
1063 pDocument->pClass->postUnoCommand(pDocument, ".uno:Insert",
1064 "{ \"Name\": { \"type\": \"string\", \"value\": \"LastSheet\" }, \"Index\": { \"type\": \"long\", \"value\": 0 } }", false);
1066 // insert the first sheet
1067 pDocument->pClass->postUnoCommand(pDocument, ".uno:Insert",
1068 "{ \"Name\": { \"type\": \"string\", \"value\": \"FirstSheet\" }, \"Index\": { \"type\": \"long\", \"value\": 1 } }", false);
1070 // rename the \"Sheet1\" (2nd now) to \"Renamed\"
1071 pDocument->pClass->postUnoCommand(pDocument, ".uno:Name",
1072 "{ \"Name\": { \"type\": \"string\", \"value\": \"Renamed\" }, \"Index\": { \"type\": \"long\", \"value\": 2 } }", false);
1074 // delete the \"Sheet2\" (3rd)
1075 pDocument->pClass->postUnoCommand(pDocument, ".uno:Remove",
1076 "{ \"Index\": { \"type\": \"long\", \"value\": 3 } }", false);
1078 Scheduler::ProcessEventsToIdle();
1079 CPPUNIT_ASSERT_EQUAL(6, pDocument->pClass->getParts(pDocument));
1081 std::vector<OString> aExpected = { "FirstSheet"_ostr, "Renamed"_ostr, "Sheet3"_ostr, "Sheet4"_ostr, "Sheet5"_ostr, "LastSheet"_ostr };
1082 for (int i = 0; i < 6; ++i)
1084 char* pPartName = pDocument->pClass->getPartName(pDocument, i);
1085 CPPUNIT_ASSERT_EQUAL(aExpected[i], OString(pPartName));
1086 free(pPartName);
1090 void DesktopLOKTest::testSheetSelections()
1092 LibLODocument_Impl* pDocument = loadDoc("sheets.ods", LOK_DOCTYPE_SPREADSHEET);
1093 pDocument->pClass->initializeForRendering(pDocument, nullptr);
1094 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
1097 * Check if selection data is correct
1099 // Values in twips
1100 int row5 = 1150;
1101 int col1 = 1100;
1102 int const col2 = 2200;
1103 int const col3 = 3300;
1104 int col4 = 4400;
1105 int col5 = 5500;
1107 // Select row 5 from column 1 through column 5
1108 pDocument->pClass->postMouseEvent(pDocument,
1109 LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
1110 col1, row5,
1111 1, 1, 0);
1112 pDocument->pClass->postMouseEvent(pDocument,
1113 LOK_MOUSEEVENT_MOUSEMOVE,
1114 col2, row5,
1115 1, 1, 0);
1116 pDocument->pClass->postMouseEvent(pDocument,
1117 LOK_MOUSEEVENT_MOUSEMOVE,
1118 col3, row5,
1119 1, 1, 0);
1120 pDocument->pClass->postMouseEvent(pDocument,
1121 LOK_MOUSEEVENT_MOUSEMOVE,
1122 col4, row5,
1123 1, 1, 0);
1124 pDocument->pClass->postMouseEvent(pDocument,
1125 LOK_MOUSEEVENT_MOUSEMOVE,
1126 col5, row5,
1127 1, 1, 0);
1128 pDocument->pClass->postMouseEvent(pDocument,
1129 LOK_MOUSEEVENT_MOUSEBUTTONUP,
1130 col5, row5,
1131 1, 1, 0);
1132 Scheduler::ProcessEventsToIdle();
1134 // Copy the contents and check if matches expected data
1136 char* pUsedMimeType = nullptr;
1137 char* pCopiedContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pUsedMimeType);
1138 std::vector<long> aExpected = {5, 6, 7, 8, 9};
1139 std::istringstream iss(pCopiedContent);
1140 for (const long nIndex : aExpected)
1142 std::string token;
1143 iss >> token;
1144 CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10));
1147 free(pUsedMimeType);
1148 free(pCopiedContent);
1152 * Check if clicking inside the selection deselects the whole selection
1155 // Click at row5, col4
1156 pDocument->pClass->postMouseEvent(pDocument,
1157 LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
1158 col4, row5,
1159 1, 1, 0);
1160 pDocument->pClass->postMouseEvent(pDocument,
1161 LOK_MOUSEEVENT_MOUSEBUTTONUP,
1162 col4, row5,
1163 1, 1, 0);
1164 Scheduler::ProcessEventsToIdle();
1166 // Selected text should get deselected and copying should give us
1167 // content of only one cell, now
1169 char* pUsedMimeType = nullptr;
1170 char* pCopiedContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pUsedMimeType);
1171 std::vector<long> aExpected = { 8 };
1172 std::istringstream iss(pCopiedContent);
1173 for (const long nIndex : aExpected)
1175 std::string token;
1176 iss >> token;
1177 CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10));
1180 free(pUsedMimeType);
1181 free(pCopiedContent);
1185 void DesktopLOKTest::testSheetDragDrop()
1187 LibLODocument_Impl* pDocument = loadDoc("sheets.ods", LOK_DOCTYPE_SPREADSHEET);
1188 pDocument->pClass->initializeForRendering(pDocument, nullptr);
1189 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
1191 int row01 = 100;
1192 int col01 = 1100;
1193 int col02 = 2200;
1194 int col03 = 3300;
1195 int col05 = 5500;
1196 int col07 = 5700;
1198 // Select row 01 from column 01 through column 05
1199 pDocument->pClass->postMouseEvent(pDocument,
1200 LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
1201 col01, row01,
1202 1, 1, 0);
1203 pDocument->pClass->postMouseEvent(pDocument,
1204 LOK_MOUSEEVENT_MOUSEMOVE,
1205 col02, row01,
1206 1, 1, 0);
1207 pDocument->pClass->postMouseEvent(pDocument,
1208 LOK_MOUSEEVENT_MOUSEMOVE,
1209 col05, row01,
1210 1, 1, 0);
1211 pDocument->pClass->postMouseEvent(pDocument,
1212 LOK_MOUSEEVENT_MOUSEBUTTONUP,
1213 col05, row01,
1214 1, 1, 0);
1216 Scheduler::ProcessEventsToIdle();
1218 SfxViewShell* pViewShell = SfxViewShell::Current();
1219 SfxViewFrame& rViewFrame = pViewShell->GetViewFrame();
1221 OUString sValue;
1222 css::uno::Any aValue;
1223 css::util::URL aURL;
1224 std::unique_ptr<SfxPoolItem> pState;
1226 aURL.Protocol = ".uno:";
1227 aURL.Complete = ".uno:Address";
1228 aURL.Path = "Address";
1229 aURL.Main = ".uno:Address";
1231 rViewFrame.GetBindings().QueryState(rViewFrame.GetBindings().QuerySlotId(aURL), pState);
1232 pState->QueryValue(aValue);
1233 aValue >>= sValue;
1234 CPPUNIT_ASSERT_EQUAL(u"Sheet5.A1:E1"_ustr, sValue);
1237 // Check selection content
1239 char* pMimeType = nullptr;
1240 char* pContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pMimeType);
1241 std::vector<long> aExpected = {1, 2, 3, 4, 5};
1242 std::istringstream aContent(pContent);
1243 std::string token;
1244 for (const long nIndex : aExpected)
1246 aContent >> token;
1247 CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10));
1250 free(pMimeType);
1251 free(pContent);
1254 // drag and drop
1255 pDocument->pClass->postMouseEvent(pDocument,
1256 LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
1257 col01, row01,
1258 1, 1, 0);
1259 pDocument->pClass->postMouseEvent(pDocument,
1260 LOK_MOUSEEVENT_MOUSEMOVE,
1261 col02, row01,
1262 1, 1, 0);
1263 pDocument->pClass->postMouseEvent(pDocument,
1264 LOK_MOUSEEVENT_MOUSEMOVE,
1265 col03, row01,
1266 1, 1, 0);
1267 pDocument->pClass->postMouseEvent(pDocument,
1268 LOK_MOUSEEVENT_MOUSEBUTTONUP,
1269 col07, row01,
1270 1, 1, 0);
1272 Scheduler::ProcessEventsToIdle();
1274 SfxViewShell* pViewShell = SfxViewShell::Current();
1275 SfxViewFrame& rViewFrame = pViewShell->GetViewFrame();
1277 OUString sValue;
1278 css::uno::Any aValue;
1279 css::util::URL aURL;
1280 std::unique_ptr<SfxPoolItem> pState;
1282 aURL.Protocol = ".uno:";
1283 aURL.Complete = ".uno:Address";
1284 aURL.Path = "Address";
1285 aURL.Main = ".uno:Address";
1287 rViewFrame.GetBindings().QueryState(rViewFrame.GetBindings().QuerySlotId(aURL), pState);
1288 pState->QueryValue(aValue);
1289 aValue >>= sValue;
1290 CPPUNIT_ASSERT_EQUAL(u"Sheet5.D1:H1"_ustr, sValue);
1293 // Check selection content
1295 char* pMimeType = nullptr;
1296 char* pContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pMimeType);
1297 std::vector<long> aExpected = {1, 2, 3, 4, 5};
1298 std::istringstream aContent(pContent);
1299 std::string token;
1300 for (const long nIndex : aExpected)
1302 aContent >> token;
1303 CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10));
1306 free(pMimeType);
1307 free(pContent);
1311 namespace {
1313 void verifyContextMenuStructure(boost::property_tree::ptree& aRoot)
1315 for (const auto& aItemPair: aRoot)
1317 // This is an array, so no key
1318 CPPUNIT_ASSERT_EQUAL(aItemPair.first, std::string(""));
1320 boost::property_tree::ptree aItemValue = aItemPair.second;
1321 boost::optional<boost::property_tree::ptree&> aText = aItemValue.get_child_optional("text");
1322 boost::optional<boost::property_tree::ptree&> aType = aItemValue.get_child_optional("type");
1323 boost::optional<boost::property_tree::ptree&> aCommand = aItemValue.get_child_optional("command");
1324 boost::optional<boost::property_tree::ptree&> aSubmenu = aItemValue.get_child_optional("menu");
1325 boost::optional<boost::property_tree::ptree&> aEnabled = aItemValue.get_child_optional("enabled");
1326 boost::optional<boost::property_tree::ptree&> aChecktype = aItemValue.get_child_optional("checktype");
1327 boost::optional<boost::property_tree::ptree&> aChecked = aItemValue.get_child_optional("checked");
1329 // type is omnipresent
1330 CPPUNIT_ASSERT( aType );
1332 // separator doesn't have any other attribs
1333 if ( aType.get().data() == "separator" )
1335 CPPUNIT_ASSERT( !aText );
1336 CPPUNIT_ASSERT( !aCommand );
1337 CPPUNIT_ASSERT( !aSubmenu );
1338 CPPUNIT_ASSERT( !aEnabled );
1339 CPPUNIT_ASSERT( !aChecktype );
1340 CPPUNIT_ASSERT( !aChecked );
1342 else if ( aType.get().data() == "command" )
1344 CPPUNIT_ASSERT( aCommand );
1345 CPPUNIT_ASSERT( aText );
1347 else if ( aType.get().data() == "menu")
1349 CPPUNIT_ASSERT( aSubmenu );
1350 CPPUNIT_ASSERT( aText );
1351 verifyContextMenuStructure( aSubmenu.get() );
1354 if ( aChecktype )
1356 CPPUNIT_ASSERT( aChecktype.get().data() == "radio" ||
1357 aChecktype.get().data() == "checkmark" ||
1358 aChecktype.get().data() == "auto" );
1360 CPPUNIT_ASSERT( aChecked );
1361 CPPUNIT_ASSERT( aChecked.get().data() == "true" || aChecked.get().data() == "false" );
1367 boost::optional<boost::property_tree::ptree>
1368 getContextMenuItem(boost::property_tree::ptree& aMenu, std::string const & unoSelector)
1370 boost::optional<boost::property_tree::ptree> aMenuItem;
1371 for (const auto& aItemPair: aMenu)
1373 boost::property_tree::ptree aItemValue = aItemPair.second;
1375 boost::optional<boost::property_tree::ptree&> aCommand = aItemValue.get_child_optional("command");
1376 if (aCommand && aCommand.get().data() == unoSelector )
1378 aMenuItem = aItemValue;
1379 break;
1383 return aMenuItem;
1386 } // end anonymous namespace
1388 void DesktopLOKTest::testContextMenuCalc()
1390 LibLODocument_Impl* pDocument = loadDoc("sheet_with_image.ods", LOK_DOCTYPE_SPREADSHEET);
1391 pDocument->pClass->initializeForRendering(pDocument, nullptr);
1392 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
1394 // Values in twips
1395 Point aPointOnImage(1150, 1100);
1396 pDocument->pClass->postMouseEvent(pDocument,
1397 LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
1398 aPointOnImage.X(), aPointOnImage.Y(),
1399 1, 4, 0);
1400 Scheduler::ProcessEventsToIdle();
1402 TimeValue const aTimeValue = {2 , 0}; // 2 seconds max
1403 m_aContextMenuCondition.wait(aTimeValue);
1405 CPPUNIT_ASSERT( !m_aContextMenuResult.empty() );
1406 boost::optional<boost::property_tree::ptree&> aMenu = m_aContextMenuResult.get_child_optional("menu");
1407 CPPUNIT_ASSERT( aMenu );
1408 verifyContextMenuStructure( aMenu.get() );
1410 // tests for calc specific context menu
1411 // Cut is enabled
1413 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut");
1414 CPPUNIT_ASSERT(aMenuItem);
1416 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1417 CPPUNIT_ASSERT(aEnabled);
1418 CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true"));
1421 // Copy is enabled
1423 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy");
1424 CPPUNIT_ASSERT(aMenuItem);
1426 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1427 CPPUNIT_ASSERT(aEnabled);
1428 CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true"));
1431 // Paste is enabled
1433 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste");
1434 CPPUNIT_ASSERT(aMenuItem);
1436 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1437 CPPUNIT_ASSERT(aEnabled);
1438 CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true"));
1441 // Remove hyperlink is disabled
1443 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:RemoveHyperlink");
1444 CPPUNIT_ASSERT(aMenuItem);
1446 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1447 CPPUNIT_ASSERT(aEnabled);
1448 CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false"));
1451 // open hyperlink is disabled
1453 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:OpenHyperlinkOnCursor");
1454 CPPUNIT_ASSERT(aMenuItem);
1456 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1457 CPPUNIT_ASSERT(aEnabled);
1458 CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false"));
1461 // checkbutton tests
1463 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:AnchorMenu");
1464 CPPUNIT_ASSERT(aMenuItem);
1466 boost::optional<boost::property_tree::ptree&> aSubmenu = aMenuItem.get().get_child_optional("menu");
1467 CPPUNIT_ASSERT(aSubmenu);
1469 boost::optional<boost::property_tree::ptree> aMenuItemToPage = getContextMenuItem(aSubmenu.get(), ".uno:SetAnchorToPage");
1470 CPPUNIT_ASSERT(aMenuItemToPage);
1472 boost::optional<boost::property_tree::ptree> aMenuItemToCell = getContextMenuItem(aSubmenu.get(), ".uno:SetAnchorToCell");
1473 CPPUNIT_ASSERT(aMenuItemToCell);
1475 // these are radio buttons
1476 boost::optional<boost::property_tree::ptree&> aChecktypeToPage = aMenuItemToPage.get().get_child_optional("checktype");
1477 CPPUNIT_ASSERT(aChecktypeToPage);
1478 CPPUNIT_ASSERT_EQUAL(aChecktypeToPage.get().data(), std::string("radio"));
1480 boost::optional<boost::property_tree::ptree&> aChecktypeToCell = aMenuItemToCell.get().get_child_optional("checktype");
1481 CPPUNIT_ASSERT(aChecktypeToCell);
1482 CPPUNIT_ASSERT_EQUAL(aChecktypeToCell.get().data(), std::string("radio"));
1484 // ToPage is checked
1485 boost::optional<boost::property_tree::ptree&> aCheckedToPage = aMenuItemToPage.get().get_child_optional("checked");
1486 CPPUNIT_ASSERT(aCheckedToPage);
1487 CPPUNIT_ASSERT_EQUAL(aCheckedToPage.get().data(), std::string("true"));
1489 // ToCell is unchecked
1490 boost::optional<boost::property_tree::ptree&> aCheckedToCell = aMenuItemToCell.get().get_child_optional("checked");
1491 CPPUNIT_ASSERT(aCheckedToCell);
1492 CPPUNIT_ASSERT_EQUAL(aCheckedToCell.get().data(), std::string("false"));
1496 void DesktopLOKTest::testContextMenuWriter()
1498 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1499 pDocument->pClass->initializeForRendering(pDocument, nullptr);
1500 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
1502 Point aRandomPoint(1150, 1100);
1503 pDocument->pClass->postMouseEvent(pDocument,
1504 LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
1505 aRandomPoint.X(), aRandomPoint.Y(),
1506 1, 4, 0);
1507 Scheduler::ProcessEventsToIdle();
1509 TimeValue const aTimeValue = {2 , 0}; // 2 seconds max
1510 m_aContextMenuCondition.wait(aTimeValue);
1512 CPPUNIT_ASSERT( !m_aContextMenuResult.empty() );
1513 boost::optional<boost::property_tree::ptree&> aMenu = m_aContextMenuResult.get_child_optional("menu");
1514 CPPUNIT_ASSERT( aMenu );
1515 verifyContextMenuStructure( aMenu.get() );
1517 // tests for writer specific context menu
1518 // Cut is disabled
1520 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut");
1521 CPPUNIT_ASSERT(aMenuItem);
1523 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1524 CPPUNIT_ASSERT(aEnabled);
1525 CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false"));
1528 // Copy is disabled
1530 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy");
1531 CPPUNIT_ASSERT(aMenuItem);
1533 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1534 CPPUNIT_ASSERT(aEnabled);
1535 CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false"));
1538 // Paste is enabled
1540 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste");
1541 CPPUNIT_ASSERT(aMenuItem);
1543 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1544 CPPUNIT_ASSERT(aEnabled);
1545 CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true"));
1549 void DesktopLOKTest::testContextMenuImpress()
1551 LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp", LOK_DOCTYPE_PRESENTATION);
1552 pDocument->pClass->initializeForRendering(pDocument, nullptr);
1553 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
1555 // random point where we don't hit an underlying comment or text box
1556 Point aRandomPoint(10, 1150);
1557 pDocument->pClass->postMouseEvent(pDocument,
1558 LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
1559 aRandomPoint.X(), aRandomPoint.Y(),
1560 1, 4, 0);
1561 Scheduler::ProcessEventsToIdle();
1563 TimeValue const aTimeValue = {2 , 0}; // 2 seconds max
1564 m_aContextMenuCondition.wait(aTimeValue);
1566 CPPUNIT_ASSERT( !m_aContextMenuResult.empty() );
1567 boost::optional<boost::property_tree::ptree&> aMenu = m_aContextMenuResult.get_child_optional("menu");
1568 CPPUNIT_ASSERT( aMenu );
1569 verifyContextMenuStructure( aMenu.get() );
1571 // tests for impress specific context menu
1572 // Cut is disabled
1574 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut");
1575 CPPUNIT_ASSERT(aMenuItem);
1577 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1578 CPPUNIT_ASSERT(aEnabled);
1579 CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false"));
1582 // Copy is disabled
1584 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy");
1585 CPPUNIT_ASSERT(aMenuItem);
1587 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1588 CPPUNIT_ASSERT(aEnabled);
1589 CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false"));
1592 // Paste is enabled
1594 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste");
1595 CPPUNIT_ASSERT(aMenuItem);
1597 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1598 CPPUNIT_ASSERT(aEnabled);
1599 CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true"));
1602 // SaveBackground is disabled
1604 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:SaveBackground");
1605 CPPUNIT_ASSERT(aMenuItem);
1607 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1608 CPPUNIT_ASSERT(aEnabled);
1609 CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false"));
1612 // checkbutton tests
1614 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:ShowRuler");
1615 CPPUNIT_ASSERT(aMenuItem);
1617 boost::optional<boost::property_tree::ptree&> aChecktype = aMenuItem.get().get_child_optional("checktype");
1618 CPPUNIT_ASSERT(aChecktype);
1619 CPPUNIT_ASSERT_EQUAL(aChecktype.get().data(), std::string("checkmark"));
1621 boost::optional<boost::property_tree::ptree&> aChecked = aMenuItem.get().get_child_optional("checked");
1622 CPPUNIT_ASSERT(aChecked);
1623 CPPUNIT_ASSERT_EQUAL(aChecked.get().data(), std::string("false"));
1626 // Checkbutton tests inside SnapLines submenu
1628 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:SnapLinesMenu");
1629 CPPUNIT_ASSERT(aMenuItem);
1631 boost::optional<boost::property_tree::ptree&> aSubmenu = aMenuItem.get().get_child_optional("menu");
1632 CPPUNIT_ASSERT(aSubmenu);
1634 boost::optional<boost::property_tree::ptree> aMenuItemHelpVis = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesVisible");
1635 CPPUNIT_ASSERT(aMenuItemHelpVis);
1637 boost::optional<boost::property_tree::ptree> aMenuItemHelpUse = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesUse");
1638 CPPUNIT_ASSERT(aMenuItemHelpUse);
1640 boost::optional<boost::property_tree::ptree> aMenuItemHelpFront = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesFront");
1641 CPPUNIT_ASSERT(aMenuItemHelpFront);
1643 // these are checkmarks
1644 boost::optional<boost::property_tree::ptree&> aChecktypeHelpVis = aMenuItemHelpVis.get().get_child_optional("checktype");
1645 CPPUNIT_ASSERT(aChecktypeHelpVis);
1646 CPPUNIT_ASSERT_EQUAL(aChecktypeHelpVis.get().data(), std::string("checkmark"));
1648 boost::optional<boost::property_tree::ptree&> aChecktypeHelpUse = aMenuItemHelpUse.get().get_child_optional("checktype");
1649 CPPUNIT_ASSERT(aChecktypeHelpUse);
1650 CPPUNIT_ASSERT_EQUAL(aChecktypeHelpUse.get().data(), std::string("checkmark"));
1652 boost::optional<boost::property_tree::ptree&> aChecktypeHelpFront = aMenuItemHelpFront.get().get_child_optional("checktype");
1653 CPPUNIT_ASSERT(aChecktypeHelpFront);
1654 CPPUNIT_ASSERT_EQUAL(aChecktypeHelpFront.get().data(), std::string("checkmark"));
1656 // HelplineVisible is unchecked
1657 boost::optional<boost::property_tree::ptree&> aCheckedHelpVis = aMenuItemHelpVis.get().get_child_optional("checked");
1658 CPPUNIT_ASSERT(aCheckedHelpVis);
1659 CPPUNIT_ASSERT_EQUAL(aCheckedHelpVis.get().data(), std::string("false"));
1661 // HelplineUse is checked
1662 boost::optional<boost::property_tree::ptree&> aCheckedHelpUse = aMenuItemHelpUse.get().get_child_optional("checked");
1663 CPPUNIT_ASSERT(aCheckedHelpUse);
1664 CPPUNIT_ASSERT_EQUAL(aCheckedHelpUse.get().data(), std::string("true"));
1666 // HelplineFront is checked
1667 boost::optional<boost::property_tree::ptree&> aCheckedHelpFront = aMenuItemHelpFront.get().get_child_optional("checked");
1668 CPPUNIT_ASSERT(aCheckedHelpFront);
1669 CPPUNIT_ASSERT_EQUAL(aCheckedHelpFront.get().data(), std::string("true"));
1673 static void callbackCompressionTest(const int type, const char* payload, void* data)
1675 std::vector<std::tuple<int, std::string>>* notifs = static_cast<std::vector<std::tuple<int, std::string>>*>(data);
1676 notifs->emplace_back(type, std::string(payload ? payload : "(nil)"));
1679 void DesktopLOKTest::testNotificationCompression()
1681 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1682 std::vector<std::tuple<int, std::string>> notifs;
1683 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1684 handler->setViewId(SfxLokHelper::getView());
1686 handler->queue(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, ""_ostr); // 0
1687 handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10"_ostr); // Superseded.
1688 handler->queue(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, ""_ostr); // Should be dropped.
1689 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10"_ostr); // 1
1690 handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10"_ostr); // Should be dropped.
1691 handler->queue(LOK_CALLBACK_TEXT_SELECTION, ""_ostr); // Superseded.
1692 handler->queue(LOK_CALLBACK_STATE_CHANGED, ""_ostr); // 2
1693 handler->queue(LOK_CALLBACK_STATE_CHANGED, ".uno:Bold"_ostr); // 3
1694 handler->queue(LOK_CALLBACK_STATE_CHANGED, ""_ostr); // 4
1695 handler->queue(LOK_CALLBACK_MOUSE_POINTER, "text"_ostr); // 5
1696 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10"_ostr); // Should be dropped.
1697 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10"_ostr); // Should be dropped.
1698 handler->queue(LOK_CALLBACK_MOUSE_POINTER, "text"_ostr); // Should be dropped.
1699 handler->queue(LOK_CALLBACK_TEXT_SELECTION_START, "15, 25, 15, 10"_ostr); // Superseded.
1700 handler->queue(LOK_CALLBACK_TEXT_SELECTION_END, "15, 25, 15, 10"_ostr); // Superseded.
1701 handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10"_ostr); // Superseded.
1702 handler->queue(LOK_CALLBACK_TEXT_SELECTION_START, "15, 25, 15, 10"_ostr); // Should be dropped.
1703 handler->queue(LOK_CALLBACK_TEXT_SELECTION_END, "15, 25, 15, 10"_ostr); // Should be dropped.
1704 handler->queue(LOK_CALLBACK_TEXT_SELECTION, ""_ostr); // 7
1705 handler->queue(LOK_CALLBACK_TEXT_SELECTION_START, "15, 25, 15, 10"_ostr); // 8
1706 handler->queue(LOK_CALLBACK_TEXT_SELECTION_END, "15, 25, 15, 10"_ostr); // 9
1707 handler->queue(LOK_CALLBACK_CELL_CURSOR, "15, 25, 15, 10"_ostr); // 10
1708 handler->queue(LOK_CALLBACK_CURSOR_VISIBLE, ""_ostr); // 11
1709 handler->queue(LOK_CALLBACK_CELL_CURSOR, "15, 25, 15, 10"_ostr); // Should be dropped.
1710 handler->queue(LOK_CALLBACK_CELL_FORMULA, "blah"_ostr); // 12
1711 handler->queue(LOK_CALLBACK_SET_PART, "1"_ostr); // 13
1712 handler->queue(LOK_CALLBACK_STATE_CHANGED, ".uno:AssignLayout=20"_ostr); // Superseded
1713 handler->queue(LOK_CALLBACK_CURSOR_VISIBLE, ""_ostr); // Should be dropped.
1714 handler->queue(LOK_CALLBACK_CELL_FORMULA, "blah"_ostr); // Should be dropped.
1715 handler->queue(LOK_CALLBACK_SET_PART, "1"_ostr); // Should be dropped.
1716 handler->queue(LOK_CALLBACK_STATE_CHANGED, ".uno:AssignLayout=1"_ostr); // 14
1718 Scheduler::ProcessEventsToIdle();
1720 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(14), notifs.size());
1722 size_t i = 0;
1723 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR), std::get<0>(notifs[i]));
1724 CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++]));
1726 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1727 CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++]));
1729 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i]));
1730 CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++]));
1732 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i]));
1733 CPPUNIT_ASSERT_EQUAL(std::string(".uno:Bold"), std::get<1>(notifs[i++]));
1735 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i]));
1736 CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++]));
1738 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_MOUSE_POINTER), std::get<0>(notifs[i]));
1739 CPPUNIT_ASSERT_EQUAL(std::string("text"), std::get<1>(notifs[i++]));
1741 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_TEXT_SELECTION), std::get<0>(notifs[i]));
1742 CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++]));
1744 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_TEXT_SELECTION_START), std::get<0>(notifs[i]));
1745 CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++]));
1747 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_TEXT_SELECTION_END), std::get<0>(notifs[i]));
1748 CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++]));
1750 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_CELL_CURSOR), std::get<0>(notifs[i]));
1751 CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++]));
1753 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_CURSOR_VISIBLE), std::get<0>(notifs[i]));
1754 CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++]));
1756 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_CELL_FORMULA), std::get<0>(notifs[i]));
1757 CPPUNIT_ASSERT_EQUAL(std::string("blah"), std::get<1>(notifs[i++]));
1759 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_SET_PART), std::get<0>(notifs[i]));
1760 CPPUNIT_ASSERT_EQUAL(std::string("1"), std::get<1>(notifs[i++]));
1762 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i]));
1763 CPPUNIT_ASSERT_EQUAL(std::string(".uno:AssignLayout=1"), std::get<1>(notifs[i++]));
1766 void DesktopLOKTest::testTileInvalidationCompression()
1768 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1770 comphelper::LibreOfficeKit::setPartInInvalidation(true);
1771 comphelper::ScopeGuard aGuard([]()
1773 comphelper::LibreOfficeKit::setPartInInvalidation(false);
1776 // Single part merging
1778 std::vector<std::tuple<int, std::string>> notifs;
1779 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1780 handler->setViewId(SfxLokHelper::getView());
1782 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0, 0"_ostr);
1783 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0, 0"_ostr);
1784 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-100, -50, 500, 650, 0, 0"_ostr);
1785 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, 0, 0"_ostr);
1786 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "100, 100, 200, 200, 0, 0"_ostr);
1788 Scheduler::ProcessEventsToIdle();
1790 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size());
1792 size_t i = 0;
1793 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1794 CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 400, 600, 0, 0"), std::get<1>(notifs[i++]));
1797 // Part Number
1799 std::vector<std::tuple<int, std::string>> notifs;
1800 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1801 handler->setViewId(SfxLokHelper::getView());
1803 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0, 0"_ostr);
1804 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 1, 0"_ostr); // Different part
1805 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 0, 0, 2, 0"_ostr); // Invalid
1806 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-121, -121, 200, 200, 0, 0"_ostr); // Inside first
1807 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, 1, 0"_ostr); // Invalid
1809 Scheduler::ProcessEventsToIdle();
1811 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size());
1813 size_t i = 0;
1814 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1815 CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 200, 200, 1, 0"), std::get<1>(notifs[i++]));
1817 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1818 CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 239, 239, 0, 0"), std::get<1>(notifs[i++]));
1821 // All Parts
1823 std::vector<std::tuple<int, std::string>> notifs;
1824 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1825 handler->setViewId(SfxLokHelper::getView());
1827 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0, 0"_ostr); // 0
1828 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 1, 0"_ostr); // 1: Different part
1829 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 0, 0, -1, 0"_ostr); // Invalid
1830 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-121, -121, 200, 200, -1, 0"_ostr); // 0: All parts
1831 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, -1, 0"_ostr); // Invalid
1832 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-100, -100, 1200, 1200, -1, 0"_ostr); // 0: All parts
1833 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 3, 0"_ostr); // Overlapped
1834 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "1000, 1000, 1239, 1239, 2, 0"_ostr); // 1: Unique region
1836 Scheduler::ProcessEventsToIdle();
1838 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size());
1840 size_t i = 0;
1841 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1842 CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 1100, 1100, -1, 0"), std::get<1>(notifs[i++]));
1844 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1845 CPPUNIT_ASSERT_EQUAL(std::string("1000, 1000, 1239, 1239, 2, 0"), std::get<1>(notifs[i++]));
1848 // All Parts (partial)
1850 std::vector<std::tuple<int, std::string>> notifs;
1851 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1852 handler->setViewId(SfxLokHelper::getView());
1854 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 0, 0"_ostr); // 0
1855 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 100, 100, 1, 0"_ostr); // 1: Different part
1856 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 0, 0, -1, 0"_ostr); // Invalid
1857 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "150, 150, 50, 50, -1, 0"_ostr); // 2: All-parts
1858 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, -1, 0"_ostr); // Invalid
1859 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "150, 150, 40, 40, 3, 0"_ostr); // Overlapped w/ 2
1860 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 4, 0"_ostr); // 3: Unique
1861 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "1000, 1000, 1239, 1239, 1, 0"_ostr); // 4: Unique
1863 Scheduler::ProcessEventsToIdle();
1865 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(5), notifs.size());
1867 size_t i = 0;
1868 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1869 CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 200, 200, 0, 0"), std::get<1>(notifs[i++]));
1871 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1872 CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 100, 100, 1, 0"), std::get<1>(notifs[i++]));
1874 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1875 CPPUNIT_ASSERT_EQUAL(std::string("150, 150, 50, 50, -1, 0"), std::get<1>(notifs[i++]));
1877 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1878 CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 200, 200, 4, 0"), std::get<1>(notifs[i++]));
1880 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1881 CPPUNIT_ASSERT_EQUAL(std::string("1000, 1000, 1239, 1239, 1, 0"), std::get<1>(notifs[i++]));
1884 // Merge with "EMPTY"
1886 std::vector<std::tuple<int, std::string>> notifs;
1887 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1888 handler->setViewId(SfxLokHelper::getView());
1890 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0, 0"_ostr);
1891 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "EMPTY, 0, 0"_ostr);
1892 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 240, 0, 0"_ostr);
1893 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-121, -121, 300, 300, 0, 0"_ostr);
1894 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, 0, 0"_ostr);
1896 Scheduler::ProcessEventsToIdle();
1898 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size());
1900 size_t i = 0;
1901 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1902 CPPUNIT_ASSERT_EQUAL(std::string("EMPTY, 0, 0"), std::get<1>(notifs[i++]));
1906 void DesktopLOKTest::testPartInInvalidation()
1908 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1909 // No part in invalidation: merge.
1911 std::vector<std::tuple<int, std::string>> notifs;
1912 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1913 handler->setViewId(SfxLokHelper::getView());
1915 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10"_ostr);
1916 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "20, 10, 20, 10"_ostr);
1918 Scheduler::ProcessEventsToIdle();
1920 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size());
1922 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[0]));
1923 CPPUNIT_ASSERT_EQUAL(std::string("10, 10, 30, 10"), std::get<1>(notifs[0]));
1925 // No part in invalidation: don't merge.
1927 std::vector<std::tuple<int, std::string>> notifs;
1928 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1929 handler->setViewId(SfxLokHelper::getView());
1931 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10"_ostr);
1932 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "40, 10, 20, 10"_ostr);
1934 Scheduler::ProcessEventsToIdle();
1936 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size());
1939 // Part in invalidation, intersection and parts match -> merge.
1941 comphelper::LibreOfficeKit::setPartInInvalidation(true);
1942 comphelper::ScopeGuard aGuard([]()
1944 comphelper::LibreOfficeKit::setPartInInvalidation(false);
1947 std::vector<std::tuple<int, std::string>> notifs;
1948 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1949 handler->setViewId(SfxLokHelper::getView());
1951 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10, 0, 0"_ostr);
1952 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "20, 10, 20, 10, 0, 0"_ostr);
1954 Scheduler::ProcessEventsToIdle();
1956 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size());
1958 // Part in invalidation, intersection and parts don't match -> don't merge.
1960 comphelper::LibreOfficeKit::setPartInInvalidation(true);
1961 comphelper::ScopeGuard aGuard([]()
1963 comphelper::LibreOfficeKit::setPartInInvalidation(false);
1966 std::vector<std::tuple<int, std::string>> notifs;
1967 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1968 handler->setViewId(SfxLokHelper::getView());
1970 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10, 0, 0"_ostr);
1971 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "20, 10, 20, 10, 1, 0"_ostr);
1973 Scheduler::ProcessEventsToIdle();
1975 // This failed as RectangleAndPart::Create() always assumed no part in
1976 // payload, so this was merged -> it was 1.
1977 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size());
1981 static void callbackBinaryCallbackTest(const int type, const char* payload, void* data)
1983 std::vector<std::tuple<int, std::string>>* notifs = static_cast<std::vector<std::tuple<int, std::string>>*>(data);
1984 notifs->emplace_back(type, std::string(payload ? payload : "(nil)"));
1987 void DesktopLOKTest::testBinaryCallback()
1989 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1991 const tools::Rectangle rect1(Point(10,15),Size(20,25));
1992 const std::string rect1String(rect1.toString());
1993 // Verify that using queue() and libreOfficeKitViewInvalidateTilesCallback() has the same result.
1995 std::vector<std::tuple<int, std::string>> notifs;
1996 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackBinaryCallbackTest, &notifs));
1997 handler->setViewId(SfxLokHelper::getView());
1999 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, OString(rect1String));
2001 Scheduler::ProcessEventsToIdle();
2003 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size());
2004 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[0]));
2005 CPPUNIT_ASSERT_EQUAL(rect1String, std::get<1>(notifs[0]));
2008 std::vector<std::tuple<int, std::string>> notifs;
2009 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackBinaryCallbackTest, &notifs));
2010 handler->setViewId(SfxLokHelper::getView());
2012 handler->libreOfficeKitViewInvalidateTilesCallback(&rect1, INT_MIN, 0);
2014 Scheduler::ProcessEventsToIdle();
2016 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size());
2017 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[0]));
2018 CPPUNIT_ASSERT_EQUAL(rect1String, std::get<1>(notifs[0]));
2020 // Verify that the "EMPTY" invalidation gets converted properly.
2022 std::vector<std::tuple<int, std::string>> notifs;
2023 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackBinaryCallbackTest, &notifs));
2024 handler->setViewId(SfxLokHelper::getView());
2026 handler->libreOfficeKitViewInvalidateTilesCallback(nullptr, INT_MIN, 0);
2028 Scheduler::ProcessEventsToIdle();
2030 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size());
2031 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[0]));
2032 CPPUNIT_ASSERT_EQUAL(std::string("EMPTY"), std::get<1>(notifs[0]));
2036 void DesktopLOKTest::testInput()
2038 // Load a Writer document, enable change recording and press a key.
2039 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
2041 Scheduler::ProcessEventsToIdle(); // Get focus & other bits setup.
2043 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "far");
2044 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "far");
2045 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, " ");
2046 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, " ");
2047 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "beyond");
2048 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "beyond");
2049 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, " ");
2050 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, " ");
2051 // Mis-spelled ...
2052 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "kovely");
2053 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "kovely");
2054 // Remove it again
2055 pDocument->pClass->removeTextContext(pDocument, 0, 6, 0);
2056 // Replace it with lovely
2057 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "lovely");
2058 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "lovely");
2059 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, " ");
2060 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, " ");
2062 // get the text ...
2063 pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false);
2064 Scheduler::ProcessEventsToIdle();
2065 char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr);
2066 CPPUNIT_ASSERT(pText != nullptr);
2067 CPPUNIT_ASSERT_EQUAL("far beyond lovely "_ostr, OString(pText));
2068 free(pText);
2071 void DesktopLOKTest::testRedlineWriter()
2073 // Load a Writer document, enable change recording and press a key.
2074 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
2075 uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
2076 xPropertySet->setPropertyValue(u"RecordChanges"_ustr, uno::Any(true));
2077 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0);
2078 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't', 0);
2079 Scheduler::ProcessEventsToIdle();
2081 // Get redline info.
2082 boost::property_tree::ptree aTree;
2083 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:AcceptTrackedChanges");
2084 std::stringstream aStream(pJSON);
2085 free(pJSON);
2086 CPPUNIT_ASSERT(!aStream.str().empty());
2087 boost::property_tree::read_json(aStream, aTree);
2088 // Make sure that pressing a key creates exactly one redline.
2089 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aTree.get_child("redlines").size());
2091 for (const boost::property_tree::ptree::value_type& rRedline : aTree.get_child("redlines"))
2092 // This failed with boost::property_tree::ptree_bad_path, as there were no description field.
2093 CPPUNIT_ASSERT_EQUAL(std::string("Insert \xE2\x80\x9Ct\xE2\x80\x9D"), rRedline.second.get<std::string>("description"));
2094 // U+201C LEFT DOUBLE QUOTATION MARK, U+201D RIGHT DOUBLE QUOTATION
2095 // MARK
2098 void DesktopLOKTest::testRedlineCalc()
2100 // Load a Writer document, enable change recording and press a key.
2101 LibLODocument_Impl* pDocument = loadDoc("sheets.ods");
2102 uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
2103 xPropertySet->setPropertyValue(u"RecordChanges"_ustr, uno::Any(true));
2104 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0);
2105 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't', 0);
2106 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN);
2107 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, KEY_RETURN);
2108 Scheduler::ProcessEventsToIdle();
2110 // Get redline info.
2111 boost::property_tree::ptree aTree;
2112 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:AcceptTrackedChanges");
2113 std::stringstream aStream(pJSON);
2114 free(pJSON);
2115 CPPUNIT_ASSERT(!aStream.str().empty());
2116 boost::property_tree::read_json(aStream, aTree);
2117 // Make sure that pressing a key creates exactly one redline.
2118 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aTree.get_child("redlines").size());
2120 for (const boost::property_tree::ptree::value_type& rRedline : aTree.get_child("redlines"))
2121 // This failed with boost::property_tree::ptree_bad_path, as there were no description field.
2122 CPPUNIT_ASSERT_EQUAL(std::string("Cell B4 changed from '5' to 't'"), rRedline.second.get<std::string>("description"));
2125 namespace {
2127 class ViewCallback
2129 LibLODocument_Impl* mpDocument;
2130 int mnView;
2131 public:
2132 OString m_aCellFormula;
2133 int m_nTableSelectionCount;
2134 int m_nColorPaletteCallbackCount = 0;
2135 bool m_bEmptyTableSelection;
2136 bool m_bTilesInvalidated;
2137 bool m_bZeroCursor;
2138 tools::Rectangle m_aOwnCursor;
2139 boost::property_tree::ptree m_aCommentCallbackResult;
2140 boost::property_tree::ptree m_aColorPaletteCallbackResult;
2142 ViewCallback(LibLODocument_Impl* pDocument)
2143 : mpDocument(pDocument),
2144 m_nTableSelectionCount(0),
2145 m_bEmptyTableSelection(false),
2146 m_bTilesInvalidated(false),
2147 m_bZeroCursor(false)
2149 mnView = SfxLokHelper::getView();
2150 mpDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, this);
2153 ~ViewCallback()
2155 mpDocument->m_pDocumentClass->setView(mpDocument, mnView);
2156 mpDocument->m_pDocumentClass->registerCallback(mpDocument, nullptr, nullptr);
2159 static void callback(int nType, const char* pPayload, void* pData)
2161 static_cast<ViewCallback*>(pData)->callbackImpl(nType, pPayload);
2164 void callbackImpl(int nType, const char* pPayload)
2166 OString aPayload(pPayload);
2167 switch (nType)
2169 case LOK_CALLBACK_INVALIDATE_TILES:
2171 m_bTilesInvalidated = true;
2173 break;
2174 case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
2176 uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::fromUtf8(aPayload));
2177 if (std::string_view("EMPTY") == pPayload)
2178 return;
2179 CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(4), aSeq.getLength());
2180 m_aOwnCursor.SetLeft(aSeq[0].toInt32());
2181 m_aOwnCursor.SetTop(aSeq[1].toInt32());
2182 m_aOwnCursor.setWidth(aSeq[2].toInt32());
2183 m_aOwnCursor.setHeight(aSeq[3].toInt32());
2185 if (m_aOwnCursor.Left() == 0 && m_aOwnCursor.Top() == 0)
2186 m_bZeroCursor = true;
2188 break;
2189 case LOK_CALLBACK_COMMENT:
2191 m_aCommentCallbackResult.clear();
2192 std::stringstream aStream(pPayload);
2193 boost::property_tree::read_json(aStream, m_aCommentCallbackResult);
2194 m_aCommentCallbackResult = m_aCommentCallbackResult.get_child("comment");
2196 break;
2197 break;
2198 case LOK_CALLBACK_CELL_FORMULA:
2200 m_aCellFormula = aPayload;
2202 break;
2203 case LOK_CALLBACK_TABLE_SELECTED:
2205 m_bEmptyTableSelection = (std::string(pPayload).compare("{ }") == 0);
2206 ++m_nTableSelectionCount;
2208 break;
2209 case LOK_CALLBACK_COLOR_PALETTES:
2211 m_aColorPaletteCallbackResult.clear();
2212 std::stringstream aStream(pPayload);
2213 boost::property_tree::read_json(aStream, m_aColorPaletteCallbackResult);
2214 ++m_nColorPaletteCallbackCount;
2216 break;
2223 void DesktopLOKTest::testPaintPartTile()
2225 // Load an impress doc of 2 slides.
2226 // ViewCallback aView1;
2227 // ViewCallback aView2;
2228 LibLODocument_Impl* pDocument = loadDoc("2slides.odp");
2229 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2230 // pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView1);
2231 int nView1 = pDocument->m_pDocumentClass->getView(pDocument);
2233 // Create a second view.
2234 pDocument->m_pDocumentClass->createView(pDocument);
2235 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2236 // pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView2);
2238 // Go to the second slide in the second view.
2239 pDocument->m_pDocumentClass->setPart(pDocument, 1);
2241 // Switch back to the first view and start typing.
2242 pDocument->m_pDocumentClass->setView(pDocument, nView1);
2243 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, awt::Key::TAB);
2244 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, awt::Key::TAB);
2245 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'x', 0);
2246 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'x', 0);
2247 Scheduler::ProcessEventsToIdle();
2249 // Call paintPartTile() to paint the second part (in whichever view it finds suitable for this).
2250 unsigned char pPixels[256 * 256 * 4];
2251 pDocument->m_pDocumentClass->paintPartTile(pDocument, pPixels, 1, 0, 256, 256, 0, 0, 256, 256);
2253 // Type again.
2254 Scheduler::ProcessEventsToIdle();
2255 // aView1.m_bTilesInvalidated = false;
2256 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'x', 0);
2257 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'x', 0);
2258 Scheduler::ProcessEventsToIdle();
2259 // This failed: paintPartTile() (as a side-effect) ended the text edit of
2260 // the first view, so there were no invalidations.
2261 //CPPUNIT_ASSERT(aView1.m_bTilesInvalidated);
2264 void DesktopLOKTest::testPaintPartTileDifferentSchemes()
2266 Color aDarkColor(0x1c, 0x1c, 0x1c);
2268 // Add a minimal dark scheme
2270 svtools::EditableColorConfig aColorConfig;
2271 svtools::ColorConfigValue aValue;
2272 aValue.bIsVisible = true;
2273 aValue.nColor = aDarkColor;
2274 aColorConfig.SetColorValue(svtools::DOCCOLOR, aValue);
2275 aColorConfig.AddScheme(u"Dark"_ustr);
2278 // Add a minimal light scheme
2280 svtools::EditableColorConfig aColorConfig;
2281 svtools::ColorConfigValue aValue;
2282 aValue.bIsVisible = true;
2283 aValue.nColor = COL_WHITE;
2284 aColorConfig.SetColorValue(svtools::DOCCOLOR, aValue);
2285 aColorConfig.AddScheme(u"Light"_ustr);
2288 // This view will default to light scheme
2289 LibLODocument_Impl* pDocument = loadDoc("2slides.odp");
2290 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2291 int nView1 = pDocument->m_pDocumentClass->getView(pDocument);
2293 // Create a second view
2294 pDocument->m_pDocumentClass->createView(pDocument);
2295 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2297 // Go to the second slide in the second view
2298 pDocument->m_pDocumentClass->setPart(pDocument, 1);
2300 // Set to dark scheme
2302 uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence(
2304 { "NewTheme", uno::Any(u"Dark"_ustr) },
2307 dispatchCommand(mxComponent, u".uno:ChangeTheme"_ustr, aPropertyValues);
2310 constexpr int nCanvasWidth = 256;
2311 constexpr int nCanvasHeight = 256;
2313 // Just a random pixel in the middle of the canvas
2314 constexpr int nPixelX = 128;
2315 constexpr int nPixelY = 128 * nCanvasWidth;
2317 std::array<sal_uInt8, nCanvasWidth * nCanvasHeight * 4> aPixels;
2319 // Both parts should be painted with dark scheme
2320 pDocument->m_pDocumentClass->paintPartTile(pDocument, aPixels.data(), 0, 0, nCanvasWidth, nCanvasHeight, 0, 0, nCanvasWidth, nCanvasHeight);
2321 Color aPixel(aPixels[nPixelX + nPixelY + 0], aPixels[nPixelX + nPixelY + 1], aPixels[nPixelX + nPixelY + 2]);
2322 CPPUNIT_ASSERT_EQUAL(aDarkColor, aPixel);
2324 pDocument->m_pDocumentClass->paintPartTile(pDocument, aPixels.data(), 0, 0, nCanvasWidth, nCanvasHeight, 0, 0, nCanvasWidth, nCanvasHeight);
2325 aPixel = Color(aPixels[nPixelX + nPixelY + 0], aPixels[nPixelX + nPixelY + 1], aPixels[nPixelX + nPixelY + 2]);
2326 CPPUNIT_ASSERT_EQUAL(aDarkColor, aPixel);
2328 // Switch back to first view
2329 pDocument->m_pDocumentClass->setView(pDocument, nView1);
2331 // Both parts should be painted with light scheme
2332 pDocument->m_pDocumentClass->paintPartTile(pDocument, aPixels.data(), 0, 0, nCanvasWidth, nCanvasHeight, 0, 0, nCanvasWidth, nCanvasHeight);
2333 aPixel = Color(aPixels[nPixelX + nPixelY + 0], aPixels[nPixelX + nPixelY + 1], aPixels[nPixelX + nPixelY + 2]);
2334 CPPUNIT_ASSERT_EQUAL(COL_WHITE, aPixel);
2336 pDocument->m_pDocumentClass->paintPartTile(pDocument, aPixels.data(), 0, 0, nCanvasWidth, nCanvasHeight, 0, 0, nCanvasWidth, nCanvasHeight);
2337 aPixel = Color(aPixels[nPixelX + nPixelY + 0], aPixels[nPixelX + nPixelY + 1], aPixels[nPixelX + nPixelY + 2]);
2338 CPPUNIT_ASSERT_EQUAL(COL_WHITE, aPixel);
2341 #if HAVE_MORE_FONTS
2342 #include <rtl/uri.hxx>
2343 void DesktopLOKTest::testGetFontSubset()
2345 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
2346 OUString aFontName = rtl::Uri::encode(
2347 u"Liberation Sans"_ustr,
2348 rtl_UriCharClassRelSegment,
2349 rtl_UriEncodeKeepEscapes,
2350 RTL_TEXTENCODING_UTF8
2352 OString aCommand = ".uno:FontSubset&name=" + OUStringToOString(aFontName, RTL_TEXTENCODING_UTF8);
2353 boost::property_tree::ptree aTree;
2354 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aCommand.getStr());
2355 std::stringstream aStream(pJSON);
2356 boost::property_tree::read_json(aStream, aTree);
2357 CPPUNIT_ASSERT( !aTree.empty() );
2358 CPPUNIT_ASSERT_EQUAL( std::string(".uno:FontSubset"), aTree.get_child("commandName").get_value<std::string>() );
2359 boost::property_tree::ptree aValues = aTree.get_child("commandValues");
2360 CPPUNIT_ASSERT( !aValues.empty() );
2361 free(pJSON);
2363 #endif
2365 void DesktopLOKTest::testCommentsWriter()
2367 // Disable tiled rendering for comments
2368 comphelper::LibreOfficeKit::setTiledAnnotations(false);
2370 LibLODocument_Impl* pDocument = loadDoc("comments.odt");
2371 pDocument->m_pDocumentClass->initializeForRendering(pDocument, nullptr);
2372 tools::Long nWidth, nHeight;
2373 pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight);
2375 // Document width alongwith without sidebar comes to be < 13000
2376 CPPUNIT_ASSERT( nWidth < 13000 );
2378 // Can we get all the comments using .uno:ViewAnnotations command ?
2379 boost::property_tree::ptree aTree;
2380 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations");
2381 std::stringstream aStream(pJSON);
2382 free(pJSON);
2383 CPPUNIT_ASSERT(!aStream.str().empty());
2384 boost::property_tree::read_json(aStream, aTree);
2385 // There are 3 comments in the document already
2386 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aTree.get_child("comments").size());
2388 int nComment2Id = 0;
2389 // Check if all comment fields have valid data
2390 for (const auto& rComment : aTree.get_child("comments"))
2392 CPPUNIT_ASSERT(rComment.second.get<int>("id") > 0);
2393 CPPUNIT_ASSERT(!rComment.second.get<std::string>("author").empty());
2394 CPPUNIT_ASSERT(!rComment.second.get<std::string>("html").empty());
2395 // Has a valid iso 8601 date time string
2396 css::util::DateTime aDateTime;
2397 OUString aDateTimeString = OUString::createFromAscii(rComment.second.get<std::string>("dateTime"));
2398 CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime));
2400 // This comment has a marked text range
2401 if (rComment.second.get<std::string>("html") == "<div>Comment 2</div>")
2403 CPPUNIT_ASSERT(!rComment.second.get<std::string>("textRange").empty());
2404 nComment2Id = rComment.second.get<int>("id");
2406 // This is a reply comment
2407 else if (rComment.second.get<std::string>("html") == "<div>Reply to Comment 2</div>")
2409 CPPUNIT_ASSERT_EQUAL(nComment2Id, rComment.second.get<int>("parentId"));
2413 comphelper::LibreOfficeKit::setTiledAnnotations(true);
2417 void DesktopLOKTest::testCommentsCalc()
2419 // Disable tiled rendering for comments
2420 comphelper::LibreOfficeKit::setTiledAnnotations(false);
2422 LibLODocument_Impl* pDocument = loadDoc("sheets.ods");
2423 pDocument->m_pDocumentClass->initializeForRendering(pDocument, nullptr);
2425 // Can we get all the comments using .uno:ViewAnnotations command ?
2426 boost::property_tree::ptree aTree;
2427 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations");
2428 std::stringstream aStream(pJSON);
2429 free(pJSON);
2430 CPPUNIT_ASSERT(!aStream.str().empty());
2431 boost::property_tree::read_json(aStream, aTree);
2432 // There are 2 comments in the document already
2433 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aTree.get_child("comments").size());
2435 // Check if all comment fields have valid data
2436 int nIdx = 0;
2437 for (const auto& rComment : aTree.get_child("comments"))
2439 switch(nIdx)
2441 case 0:
2443 CPPUNIT_ASSERT_EQUAL(std::string("4"), rComment.second.get<std::string>("tab"));
2444 CPPUNIT_ASSERT_EQUAL(std::string("Comment1"), rComment.second.get<std::string>("text"));
2445 CPPUNIT_ASSERT_EQUAL(std::string("6 14 6 14"), rComment.second.get<std::string>("cellRange"));
2447 break;
2448 case 1:
2450 CPPUNIT_ASSERT_EQUAL(std::string("4"), rComment.second.get<std::string>("tab"));
2451 CPPUNIT_ASSERT_EQUAL(std::string("Comment2"), rComment.second.get<std::string>("text"));
2452 CPPUNIT_ASSERT_EQUAL(std::string("7 17 7 17"), rComment.second.get<std::string>("cellRange"));
2454 break;
2457 ++nIdx;
2460 // We checked all the comments
2461 CPPUNIT_ASSERT_EQUAL(2, nIdx);
2463 comphelper::LibreOfficeKit::setTiledAnnotations(true);
2467 void DesktopLOKTest::testCommentsImpress()
2469 // Disable tiled rendering for comments
2470 comphelper::LibreOfficeKit::setTiledAnnotations(false);
2472 LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp");
2473 pDocument->m_pDocumentClass->initializeForRendering(pDocument, nullptr);
2475 // Can we get all the comments using .uno:ViewAnnotations command ?
2476 boost::property_tree::ptree aTree;
2477 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations");
2478 std::stringstream aStream(pJSON);
2479 free(pJSON);
2480 CPPUNIT_ASSERT(!aStream.str().empty());
2481 boost::property_tree::read_json(aStream, aTree);
2482 // There are 2 comments in the document already
2483 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aTree.get_child("comments").size());
2485 // Check if all comment fields have valid data
2486 int nIdx = 0;
2487 for (const auto& rComment : aTree.get_child("comments"))
2489 switch(nIdx)
2491 case 0:
2493 CPPUNIT_ASSERT(rComment.second.get<int>("id") > 0);
2494 CPPUNIT_ASSERT_EQUAL(std::string("This is comment1"), rComment.second.get<std::string>("text"));
2495 CPPUNIT_ASSERT_EQUAL(std::string("LOK User1"), rComment.second.get<std::string>("author"));
2496 css::util::DateTime aDateTime;
2497 OUString aDateTimeString = OUString::createFromAscii(rComment.second.get<std::string>("dateTime"));
2498 CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime));
2500 break;
2501 case 1:
2503 CPPUNIT_ASSERT(rComment.second.get<int>("id") > 0);
2504 CPPUNIT_ASSERT_EQUAL(std::string("This is comment2"), rComment.second.get<std::string>("text"));
2505 CPPUNIT_ASSERT_EQUAL(std::string("LOK User2"), rComment.second.get<std::string>("author"));
2506 css::util::DateTime aDateTime;
2507 OUString aDateTimeString = OUString::createFromAscii(rComment.second.get<std::string>("dateTime"));
2508 CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime));
2510 break;
2513 ++nIdx;
2516 // We checked all the comments
2517 CPPUNIT_ASSERT_EQUAL(2, nIdx);
2519 comphelper::LibreOfficeKit::setTiledAnnotations(true);
2522 void DesktopLOKTest::testCommentsCallbacksWriter()
2524 // Comments callback are emitted only if tiled annotations are off
2525 comphelper::LibreOfficeKit::setTiledAnnotations(false);
2526 LibLODocument_Impl* pDocument = loadDoc("comments.odt");
2527 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2528 ViewCallback aView1(pDocument);
2529 pDocument->m_pDocumentClass->createView(pDocument);
2530 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2531 ViewCallback aView2(pDocument);
2533 // Add a new comment
2534 OString aCommandArgs("{ \"Text\": { \"type\": \"string\", \"value\": \"Additional comment\" }, \"Author\": { \"type\": \"string\", \"value\": \"LOK User1\" } }"_ostr);
2535 pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", aCommandArgs.getStr(), false);
2536 Scheduler::ProcessEventsToIdle();
2538 // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action
2539 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
2540 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
2541 int nCommentId1 = aView1.m_aCommentCallbackResult.get<int>("id");
2543 // Reply to a comment just added
2544 aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId1) + "\" }, \"Text\": { \"type\": \"string\", \"value\": \"Reply comment\" } }";
2545 pDocument->pClass->postUnoCommand(pDocument, ".uno:ReplyComment", aCommandArgs.getStr(), false);
2546 Scheduler::ProcessEventsToIdle();
2548 // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action and linked to its parent comment
2549 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
2550 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
2551 CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("parentId"));
2552 CPPUNIT_ASSERT_EQUAL(nCommentId1, aView2.m_aCommentCallbackResult.get<int>("parentId"));
2553 CPPUNIT_ASSERT_EQUAL(std::string("<div>Reply comment</div>"), aView1.m_aCommentCallbackResult.get<std::string>("html"));
2554 CPPUNIT_ASSERT_EQUAL(std::string("<div>Reply comment</div>"), aView2.m_aCommentCallbackResult.get<std::string>("html"));
2555 int nCommentId2 = aView1.m_aCommentCallbackResult.get<int>("id");
2557 // Edit the previously added comment
2558 aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId2) + "\" }, \"Text\": { \"type\": \"string\", \"value\": \"Edited comment\" } }";
2559 pDocument->pClass->postUnoCommand(pDocument, ".uno:EditAnnotation", aCommandArgs.getStr(), false);
2560 Scheduler::ProcessEventsToIdle();
2562 // We received a LOK_CALLBACK_COMMENT callback with comment 'Modify' action
2563 CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
2564 CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
2565 // parent is unchanged still
2566 CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("parentId"));
2567 CPPUNIT_ASSERT_EQUAL(nCommentId1, aView2.m_aCommentCallbackResult.get<int>("parentId"));
2568 CPPUNIT_ASSERT_EQUAL(std::string("<div>Edited comment</div>"), aView1.m_aCommentCallbackResult.get<std::string>("html"));
2569 CPPUNIT_ASSERT_EQUAL(std::string("<div>Edited comment</div>"), aView2.m_aCommentCallbackResult.get<std::string>("html"));
2571 // Delete the reply comment just added
2572 aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId2) + "\" } }";
2573 pDocument->pClass->postUnoCommand(pDocument, ".uno:DeleteComment", aCommandArgs.getStr(), false);
2574 Scheduler::ProcessEventsToIdle();
2576 // We received a LOK_CALLBACK_COMMENT callback with comment 'Remove' action
2577 CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
2578 CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
2579 CPPUNIT_ASSERT_EQUAL(nCommentId2, aView1.m_aCommentCallbackResult.get<int>("id"));
2580 CPPUNIT_ASSERT_EQUAL(nCommentId2, aView2.m_aCommentCallbackResult.get<int>("id"));
2582 // Reply to nCommentId1 again
2583 aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId1) + "\" }, \"Text\": { \"type\": \"string\", \"value\": \"Reply comment again\" } }";
2584 pDocument->pClass->postUnoCommand(pDocument, ".uno:ReplyComment", aCommandArgs.getStr(), false);
2585 Scheduler::ProcessEventsToIdle();
2587 // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action and linked to its parent comment
2588 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
2589 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
2590 CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("parentId"));
2591 CPPUNIT_ASSERT_EQUAL(nCommentId1, aView2.m_aCommentCallbackResult.get<int>("parentId"));
2592 CPPUNIT_ASSERT_EQUAL(std::string("<div>Reply comment again</div>"), aView1.m_aCommentCallbackResult.get<std::string>("html"));
2593 CPPUNIT_ASSERT_EQUAL(std::string("<div>Reply comment again</div>"), aView2.m_aCommentCallbackResult.get<std::string>("html"));
2595 // .uno:ViewAnnotations returns total of 5 comments
2596 boost::property_tree::ptree aTree;
2597 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations");
2598 std::stringstream aStream(pJSON);
2599 free(pJSON);
2600 CPPUNIT_ASSERT(!aStream.str().empty());
2601 boost::property_tree::read_json(aStream, aTree);
2602 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(5), aTree.get_child("comments").size());
2605 namespace
2608 void addParameter(tools::JsonWriter& rJson, const char* sName, std::string_view type, std::string_view value)
2610 auto testNode = rJson.startNode(sName);
2611 rJson.put("type", type);
2612 rJson.put("value", value);
2617 void DesktopLOKTest::testCommentsAddEditDeleteDraw()
2619 // Comments callback are emitted only if tiled annotations are off
2620 comphelper::LibreOfficeKit::setTiledAnnotations(false);
2621 LibLODocument_Impl* pDocument = loadDoc("BlankDrawDocument.odg");
2622 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2623 ViewCallback aView1(pDocument);
2625 // Add a new comment
2626 OString aCommandArgs;
2628 tools::JsonWriter aJson;
2629 addParameter(aJson, "Text", "string", "Comment");
2630 addParameter(aJson, "Author", "string", "LOK User1");
2631 aCommandArgs = aJson.finishAndGetAsOString();
2634 pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", aCommandArgs.getStr(), false);
2635 Scheduler::ProcessEventsToIdle();
2637 // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action
2638 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
2639 int nCommentId1 = aView1.m_aCommentCallbackResult.get<int>("id");
2641 // Edit the previously added comment
2643 tools::JsonWriter aJson;
2644 addParameter(aJson, "Id", "string", OString::number(nCommentId1));
2645 addParameter(aJson, "Text", "string", "Edited comment");
2646 aCommandArgs = aJson.finishAndGetAsOString();
2649 pDocument->pClass->postUnoCommand(pDocument, ".uno:EditAnnotation", aCommandArgs.getStr(), false);
2650 Scheduler::ProcessEventsToIdle();
2652 // We received a LOK_CALLBACK_COMMENT callback with comment 'Modify' action
2653 CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
2654 CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("id"));
2656 // Delete Comment
2658 tools::JsonWriter aJson;
2659 addParameter(aJson, "Id", "string", OString::number(nCommentId1));
2660 aCommandArgs = aJson.finishAndGetAsOString();
2662 pDocument->pClass->postUnoCommand(pDocument, ".uno:DeleteAnnotation", aCommandArgs.getStr(), false);
2663 Scheduler::ProcessEventsToIdle();
2665 // We received a LOK_CALLBACK_COMMENT callback with comment 'Remove' action
2666 CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
2667 CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("id"));
2670 void DesktopLOKTest::testCommentsInReadOnlyMode()
2672 // Comments callback are emitted only if tiled annotations are off
2673 comphelper::LibreOfficeKit::setTiledAnnotations(false);
2674 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
2676 int viewId = pDocument->m_pDocumentClass->createView(pDocument);
2677 pDocument->m_pDocumentClass->setView(pDocument, viewId);
2679 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{\".uno:Author\":{\"type\":\"string\",\"value\":\"LOK User1\"}}");
2681 SfxLokHelper::setViewReadOnly(viewId, true);
2682 SfxLokHelper::setAllowChangeComments(viewId, true);
2684 Scheduler::ProcessEventsToIdle();
2686 ViewCallback aView(pDocument);
2688 // Add a new comment
2689 OString aCommandArgs;
2691 tools::JsonWriter aJson;
2692 addParameter(aJson, "Text", "string", "Comment");
2693 addParameter(aJson, "Author", "string", "LOK User1");
2694 aCommandArgs = aJson.finishAndGetAsOString();
2697 pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", aCommandArgs.getStr(), false);
2698 Scheduler::ProcessEventsToIdle();
2700 // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action
2701 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView.m_aCommentCallbackResult.get<std::string>("action"));
2702 int nCommentId = aView.m_aCommentCallbackResult.get<int>("id");
2704 // Edit the previously added comment
2706 tools::JsonWriter aJson;
2707 addParameter(aJson, "Id", "string", OString::number(nCommentId));
2708 addParameter(aJson, "Text", "string", "Edited comment");
2709 aCommandArgs = aJson.finishAndGetAsOString();
2712 pDocument->pClass->postUnoCommand(pDocument, ".uno:EditAnnotation", aCommandArgs.getStr(), false);
2713 Scheduler::ProcessEventsToIdle();
2715 // We received a LOK_CALLBACK_COMMENT callback with comment 'Modify' action
2716 CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView.m_aCommentCallbackResult.get<std::string>("action"));
2717 CPPUNIT_ASSERT_EQUAL(nCommentId, aView.m_aCommentCallbackResult.get<int>("id"));
2719 // Delete Comment
2721 tools::JsonWriter aJson;
2722 addParameter(aJson, "Id", "string", OString::number(nCommentId));
2723 aCommandArgs = aJson.finishAndGetAsOString();
2725 pDocument->pClass->postUnoCommand(pDocument, ".uno:DeleteAnnotation", aCommandArgs.getStr(), false);
2726 Scheduler::ProcessEventsToIdle();
2728 // Result is not sent for delete operation for some reason. But it is sent when debugging with online.
2729 // TODO: Enable below 2 checks.
2731 // We received a LOK_CALLBACK_COMMENT callback with comment 'Remove' action
2732 //CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView.m_aCommentCallbackResult.get<std::string>("action"));
2733 //CPPUNIT_ASSERT_EQUAL(nCommentId, aView.m_aCommentCallbackResult.get<int>("id"));
2736 void DesktopLOKTest::testRunMacro()
2738 LibLibreOffice_Impl aOffice;
2739 bool bGoodMacro, bNonExistentMacro;
2741 // Tools macros come pre-installed in system share/basic folder,
2742 bGoodMacro = aOffice.m_pOfficeClass->runMacro(&aOffice, "macro:///Tools.Debug.ActivateReadOnlyFlag()");
2743 CPPUNIT_ASSERT(bGoodMacro);
2745 bNonExistentMacro = aOffice.m_pOfficeClass->runMacro(&aOffice, "macro:///I.Am.Not(There)");
2746 CPPUNIT_ASSERT(!bNonExistentMacro);
2749 void DesktopLOKTest::testExtractParameter()
2751 OUString aOptions(u"Language=de-DE"_ustr);
2752 OUString aValue = extractParameter(aOptions, u"Language");
2753 CPPUNIT_ASSERT_EQUAL(u"de-DE"_ustr, aValue);
2754 CPPUNIT_ASSERT_EQUAL(OUString(), aOptions);
2756 aOptions = "Language=en-US,Something";
2757 aValue = extractParameter(aOptions, u"Language");
2758 CPPUNIT_ASSERT_EQUAL(u"en-US"_ustr, aValue);
2759 CPPUNIT_ASSERT_EQUAL(u"Something"_ustr, aOptions);
2761 aOptions = "SomethingElse,Language=cs-CZ";
2762 aValue = extractParameter(aOptions, u"Language");
2763 CPPUNIT_ASSERT_EQUAL(u"cs-CZ"_ustr, aValue);
2764 CPPUNIT_ASSERT_EQUAL(u"SomethingElse"_ustr, aOptions);
2766 aOptions = "Something1,Language=hu-HU,Something2";
2767 aValue = extractParameter(aOptions, u"Language");
2768 CPPUNIT_ASSERT_EQUAL(u"hu-HU"_ustr, aValue);
2769 CPPUNIT_ASSERT_EQUAL(u"Something1,Something2"_ustr, aOptions);
2771 aOptions = "Something1,Something2=blah,Something3";
2772 aValue = extractParameter(aOptions, u"Language");
2773 CPPUNIT_ASSERT_EQUAL(OUString(), aValue);
2774 CPPUNIT_ASSERT_EQUAL(u"Something1,Something2=blah,Something3"_ustr, aOptions);
2777 void DesktopLOKTest::readFileIntoByteVector(std::u16string_view sFilename, std::vector<unsigned char> & rByteVector)
2779 rByteVector.clear();
2780 OUString aURL = createFileURL(sFilename);
2781 SvFileStream aStream(aURL, StreamMode::READ);
2782 rByteVector.resize(aStream.remainingSize());
2783 aStream.ReadBytes(rByteVector.data(), aStream.remainingSize());
2786 void DesktopLOKTest::testGetSignatureState_Signed()
2788 LibLODocument_Impl* pDocument = loadDoc("signed.odt");
2789 Scheduler::ProcessEventsToIdle();
2790 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2791 int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument);
2792 if (nState == 1)
2794 // Already SignatureState::OK, then can't test the effect of trusting new CAs.
2795 return;
2798 CPPUNIT_ASSERT_EQUAL(int(4), nState);
2800 std::vector<unsigned char> aCertificate;
2802 readFileIntoByteVector(u"rootCA.der", aCertificate);
2803 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2804 pDocument, aCertificate.data(), int(aCertificate.size()));
2805 CPPUNIT_ASSERT(bResult);
2809 readFileIntoByteVector(u"intermediateRootCA.der", aCertificate);
2810 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2811 pDocument, aCertificate.data(), int(aCertificate.size()));
2812 CPPUNIT_ASSERT(bResult);
2815 nState = pDocument->m_pDocumentClass->getSignatureState(pDocument);
2816 CPPUNIT_ASSERT_EQUAL(int(1), nState);
2819 void DesktopLOKTest::testGetSignatureState_NonSigned()
2821 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
2822 Scheduler::ProcessEventsToIdle();
2823 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2824 int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument);
2825 CPPUNIT_ASSERT_EQUAL(int(0), nState);
2828 #if 0 // broken with system nss on RHEL 7
2829 void DesktopLOKTest::testInsertCertificate_DER_ODT()
2831 // Load the document, save it into a temp file and load that file again
2832 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
2833 CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "odt", nullptr));
2834 closeDoc();
2836 pDocument = loadDocUrl(maTempFile.GetURL(), LOK_DOCTYPE_TEXT);
2838 Scheduler::ProcessEventsToIdle();
2839 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2840 Scheduler::ProcessEventsToIdle();
2842 std::vector<unsigned char> aCertificate;
2843 std::vector<unsigned char> aPrivateKey;
2846 readFileIntoByteVector(u"rootCA.der", aCertificate);
2848 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2849 pDocument, aCertificate.data(), int(aCertificate.size()));
2850 CPPUNIT_ASSERT(bResult);
2854 readFileIntoByteVector(u"intermediateRootCA.der", aCertificate);
2856 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2857 pDocument, aCertificate.data(), int(aCertificate.size()));
2858 CPPUNIT_ASSERT(bResult);
2862 readFileIntoByteVector(u"certificate.der", aCertificate);
2863 readFileIntoByteVector(u"certificatePrivateKey.der", aPrivateKey);
2865 bool bResult = pDocument->m_pDocumentClass->insertCertificate(pDocument,
2866 aCertificate.data(), int(aCertificate.size()),
2867 aPrivateKey.data(), int(aPrivateKey.size()));
2868 CPPUNIT_ASSERT(bResult);
2871 int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument);
2872 CPPUNIT_ASSERT_EQUAL(int(1), nState);
2876 void DesktopLOKTest::testInsertCertificate_PEM_ODT()
2878 // Load the document, save it into a temp file and load that file again
2879 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
2880 CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "odt", nullptr));
2881 closeDoc();
2883 pDocument = loadDocUrl(maTempFile.GetURL(), LOK_DOCTYPE_TEXT);
2885 Scheduler::ProcessEventsToIdle();
2886 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2887 Scheduler::ProcessEventsToIdle();
2889 std::vector<unsigned char> aCertificate;
2890 std::vector<unsigned char> aPrivateKey;
2893 readFileIntoByteVector(u"test-cert-chain-1.pem", aCertificate);
2895 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2896 pDocument, aCertificate.data(), int(aCertificate.size()));
2897 CPPUNIT_ASSERT(bResult);
2901 readFileIntoByteVector(u"test-cert-chain-2.pem", aCertificate);
2903 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2904 pDocument, aCertificate.data(), int(aCertificate.size()));
2905 CPPUNIT_ASSERT(bResult);
2909 readFileIntoByteVector(u"test-cert-chain-3.pem", aCertificate);
2911 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2912 pDocument, aCertificate.data(), int(aCertificate.size()));
2913 CPPUNIT_ASSERT(bResult);
2917 readFileIntoByteVector(u"test-cert-signing.pem", aCertificate);
2918 readFileIntoByteVector(u"test-PK-signing.pem", aPrivateKey);
2920 bool bResult = pDocument->m_pDocumentClass->insertCertificate(pDocument,
2921 aCertificate.data(), int(aCertificate.size()),
2922 aPrivateKey.data(), int(aPrivateKey.size()));
2923 CPPUNIT_ASSERT(bResult);
2926 int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument);
2927 CPPUNIT_ASSERT_EQUAL(int(1), nState);
2930 void DesktopLOKTest::testInsertCertificate_PEM_DOCX()
2932 // Load the document, save it into a temp file and load that file again
2933 LibLODocument_Impl* pDocument = loadDoc("blank_text.docx");
2934 CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "docx", nullptr));
2935 closeDoc();
2937 pDocument = loadDocUrl(maTempFile.GetURL(), LOK_DOCTYPE_TEXT);
2939 Scheduler::ProcessEventsToIdle();
2940 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2941 Scheduler::ProcessEventsToIdle();
2943 std::vector<unsigned char> aCertificate;
2944 std::vector<unsigned char> aPrivateKey;
2947 readFileIntoByteVector(u"test-cert-chain-1.pem", aCertificate);
2949 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2950 pDocument, aCertificate.data(), int(aCertificate.size()));
2951 CPPUNIT_ASSERT(bResult);
2955 readFileIntoByteVector(u"test-cert-chain-2.pem", aCertificate);
2957 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2958 pDocument, aCertificate.data(), int(aCertificate.size()));
2959 CPPUNIT_ASSERT(bResult);
2963 readFileIntoByteVector(u"test-cert-chain-3.pem", aCertificate);
2965 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2966 pDocument, aCertificate.data(), int(aCertificate.size()));
2967 CPPUNIT_ASSERT(bResult);
2971 readFileIntoByteVector(u"test-cert-signing.pem", aCertificate);
2972 readFileIntoByteVector(u"test-PK-signing.pem", aPrivateKey);
2974 bool bResult = pDocument->m_pDocumentClass->insertCertificate(pDocument,
2975 aCertificate.data(), int(aCertificate.size()),
2976 aPrivateKey.data(), int(aPrivateKey.size()));
2977 CPPUNIT_ASSERT(bResult);
2980 int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument);
2981 CPPUNIT_ASSERT_EQUAL(int(5), nState);
2983 #endif
2985 void DesktopLOKTest::testSignDocument_PEM_PDF()
2987 // Load the document, save it into a temp file and load that file again
2988 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
2990 Scheduler::ProcessEventsToIdle();
2991 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2992 Scheduler::ProcessEventsToIdle();
2994 std::vector<unsigned char> aCertificate;
2995 std::vector<unsigned char> aPrivateKey;
2998 readFileIntoByteVector(u"test-cert-chain-1.pem", aCertificate);
3000 bool bResult = pDocument->m_pDocumentClass->addCertificate(
3001 pDocument, aCertificate.data(), int(aCertificate.size()));
3002 CPPUNIT_ASSERT(bResult);
3006 readFileIntoByteVector(u"test-cert-chain-2.pem", aCertificate);
3008 bool bResult = pDocument->m_pDocumentClass->addCertificate(
3009 pDocument, aCertificate.data(), int(aCertificate.size()));
3010 CPPUNIT_ASSERT(bResult);
3014 readFileIntoByteVector(u"test-cert-chain-3.pem", aCertificate);
3016 bool bResult = pDocument->m_pDocumentClass->addCertificate(
3017 pDocument, aCertificate.data(), int(aCertificate.size()));
3018 CPPUNIT_ASSERT(bResult);
3021 CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "pdf", nullptr));
3023 closeDoc();
3025 Scheduler::ProcessEventsToIdle();
3027 readFileIntoByteVector(u"test-cert-signing.pem", aCertificate);
3028 readFileIntoByteVector(u"test-PK-signing.pem", aPrivateKey);
3030 LibLibreOffice_Impl aOffice;
3031 bool bResult = aOffice.m_pOfficeClass->signDocument(&aOffice, maTempFile.GetURL().toUtf8().getStr(),
3032 aCertificate.data(), int(aCertificate.size()),
3033 aPrivateKey.data(), int(aPrivateKey.size()));
3035 CPPUNIT_ASSERT(bResult);
3038 void DesktopLOKTest::testTextSelectionHandles()
3040 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
3041 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
3043 OString aText("hello"_ostr);
3044 CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8", aText.getStr(), aText.getLength()));
3046 // select the inserted text
3047 pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false);
3048 Scheduler::ProcessEventsToIdle();
3049 char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr);
3050 CPPUNIT_ASSERT_EQUAL(aText, OString(pText));
3051 free(pText);
3052 CPPUNIT_ASSERT_EQUAL("1418, 1418, 0, 275"_ostr, m_aTextSelectionStart);
3053 CPPUNIT_ASSERT_EQUAL("1897, 1418, 0, 275"_ostr, m_aTextSelectionEnd);
3055 // deselect & check
3056 m_aTextSelectionStart = ""_ostr;
3057 m_aTextSelectionEnd = ""_ostr;
3058 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::ESCAPE);
3059 Scheduler::ProcessEventsToIdle();
3060 pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr);
3061 CPPUNIT_ASSERT_EQUAL(static_cast<char *>(nullptr), pText);
3062 free(pText);
3063 CPPUNIT_ASSERT_EQUAL(OString(), m_aTextSelectionStart);
3064 CPPUNIT_ASSERT_EQUAL(OString(), m_aTextSelectionEnd);
3066 // select again; the positions of the selection handles have to be sent
3067 // again
3068 pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false);
3069 Scheduler::ProcessEventsToIdle();
3070 pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr);
3071 CPPUNIT_ASSERT_EQUAL(aText, OString(pText));
3072 free(pText);
3073 CPPUNIT_ASSERT_EQUAL("1418, 1418, 0, 275"_ostr, m_aTextSelectionStart);
3074 CPPUNIT_ASSERT_EQUAL("1897, 1418, 0, 275"_ostr, m_aTextSelectionEnd);
3077 void DesktopLOKTest::testDialogPaste()
3079 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
3080 pDocument->pClass->postUnoCommand(pDocument, ".uno:HyperlinkDialog", nullptr, false);
3081 Scheduler::ProcessEventsToIdle();
3083 SfxViewShell* pViewShell = SfxViewShell::Current();
3084 pViewShell->GetViewFrame().GetBindings().Update();
3086 VclPtr<vcl::Window> pWindow(Application::GetActiveTopWindow());
3087 CPPUNIT_ASSERT(pWindow);
3089 pDocument->pClass->postWindow(pDocument, pWindow->GetLOKWindowId(), LOK_WINDOW_PASTE,
3090 "{ \"MimeType\" : { \"type\" : \"string\", \"value\" : \"text/plain;charset=utf-8\" }, \"Data\" : { \"type\" : \"[]byte\", \"value\" : \"www.softwarelibre.org.bo\" } }");
3091 Scheduler::ProcessEventsToIdle();
3093 Control* pCtrlFocused = GetFocusControl(pWindow.get());
3094 CPPUNIT_ASSERT(pCtrlFocused);
3095 CPPUNIT_ASSERT_EQUAL(WindowType::COMBOBOX, pCtrlFocused->GetType());
3096 CPPUNIT_ASSERT_EQUAL(u"www.softwarelibre.org.bo"_ustr, pCtrlFocused->GetText());
3098 static_cast<SystemWindow*>(pWindow.get())->Close();
3099 Scheduler::ProcessEventsToIdle();
3102 void DesktopLOKTest::testComplexSelection()
3104 // Start with a blank text file and add contents.
3105 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
3106 static constexpr OString aText("hello world"_ostr);
3108 // Certainly not complex.
3109 CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionType(pDocument));
3110 CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionTypeAndText(pDocument,
3111 "", nullptr, nullptr));
3113 // Paste text.
3114 CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8", aText.getStr(), aText.getLength()));
3116 // No selection.
3117 CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionType(pDocument));
3118 CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionTypeAndText(pDocument,
3119 "", nullptr, nullptr));
3121 // Paste an image.
3122 OUString aFileURL = createFileURL(u"paste.jpg");
3123 std::ifstream aImageStream(aFileURL.toUtf8().copy(strlen("file://")).getStr());
3124 std::vector<char> aImageContents((std::istreambuf_iterator<char>(aImageStream)), std::istreambuf_iterator<char>());
3125 CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "image/jpeg", aImageContents.data(), aImageContents.size()));
3127 // Now select-all.
3128 pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false);
3129 Scheduler::ProcessEventsToIdle();
3131 // Export as plain text, we should get only the text part "hello".
3132 char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr);
3133 CPPUNIT_ASSERT(pText != nullptr);
3134 CPPUNIT_ASSERT_EQUAL(aText, OString(pText));
3135 free(pText);
3137 // Export as rtf, we should also get the image.
3138 pText = pDocument->pClass->getTextSelection(pDocument, "text/rtf", nullptr);
3139 CPPUNIT_ASSERT(pText != nullptr);
3140 CPPUNIT_ASSERT(std::string(pText).find(aText.getStr()) != std::string::npos); // Must have the text.
3141 CPPUNIT_ASSERT(std::string(pText).find("pict{") != std::string::npos); // Must have the image as well.
3142 free(pText);
3144 // Export as html, we should also get the image.
3145 pText = pDocument->pClass->getTextSelection(pDocument, "text/html", nullptr);
3146 CPPUNIT_ASSERT(pText != nullptr);
3147 CPPUNIT_ASSERT(std::string(pText).find(aText.getStr()) != std::string::npos); // Must have the text.
3148 CPPUNIT_ASSERT(std::string(pText).find("<img") != std::string::npos); // Must have the image as well.
3149 free(pText);
3151 // We expect this to be complex.
3152 CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_COMPLEX), pDocument->pClass->getSelectionType(pDocument));
3153 CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_COMPLEX), pDocument->pClass->getSelectionTypeAndText(pDocument,
3154 "", nullptr, nullptr));
3157 void DesktopLOKTest::testCalcSaveAs()
3159 LibLODocument_Impl* pDocument = loadDoc("sheets.ods");
3160 CPPUNIT_ASSERT(pDocument);
3162 // Enter some text, but don't commit.
3163 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'X', 0);
3164 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'X', 0);
3165 Scheduler::ProcessEventsToIdle();
3167 // Save as a new file.
3168 pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "ods", nullptr);
3169 closeDoc();
3171 // Load the new document and verify that the in-flight changes are saved.
3172 pDocument = loadDocUrl(maTempFile.GetURL(), LOK_DOCTYPE_SPREADSHEET);
3173 CPPUNIT_ASSERT(pDocument);
3175 ViewCallback aView(pDocument);
3176 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
3177 pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView);
3179 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT);
3180 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT);
3181 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, KEY_LEFT);
3182 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, KEY_LEFT);
3183 Scheduler::ProcessEventsToIdle();
3185 CPPUNIT_ASSERT_EQUAL("X"_ostr, aView.m_aCellFormula);
3188 void DesktopLOKTest::testSpellcheckerMultiView()
3190 static constexpr OUString aLangISO(u"en-US"_ustr);
3191 SvtSysLocaleOptions aSysLocaleOptions;
3192 aSysLocaleOptions.SetLocaleConfigString(aLangISO);
3193 aSysLocaleOptions.SetUILocaleConfigString(aLangISO);
3194 comphelper::LibreOfficeKit::setLanguageTag(LanguageTag(aLangISO, true));
3196 auto aSavedSettings = Application::GetSettings();
3197 std::unique_ptr<Resetter> pResetter(
3198 new Resetter([&]() { Application::SetSettings(aSavedSettings); }));
3199 AllSettings aSettings(aSavedSettings);
3200 aSettings.SetLanguageTag(aLangISO, true);
3201 Application::SetSettings(aSettings);
3203 LibLODocument_Impl* pDocument = loadDoc("sheet_with_image.ods", LOK_DOCTYPE_SPREADSHEET);
3204 pDocument->pClass->setViewLanguage(pDocument, 0, "en-US"); // For spellchecking.
3205 pDocument->pClass->initializeForRendering(pDocument, nullptr);
3206 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
3208 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0);
3209 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0);
3210 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0);
3211 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::ESCAPE);
3213 // Start spellchecking.
3214 pDocument->pClass->postUnoCommand(pDocument, ".uno:SpellDialog", nullptr, false);
3216 // Uncommenting this will result in a deadlock.
3217 // Because the language configuration above is not effective, and no
3218 // language is actually set, the spell-dialog finds no misspelled
3219 // words, and displays a message box, which must be dismissed to
3220 // continue.
3221 // Need to fix the language configuration issue to enable this.
3222 // Scheduler::ProcessEventsToIdle();
3224 CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument));
3226 // Now create another view.
3227 const int nViewId = pDocument->m_pDocumentClass->createView(pDocument);
3228 CPPUNIT_ASSERT_EQUAL(2, pDocument->m_pDocumentClass->getViewsCount(pDocument));
3230 // And destroy it.
3231 pDocument->m_pDocumentClass->destroyView(pDocument, nViewId);
3233 // We should survive the destroyed view.
3234 CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument));
3237 void DesktopLOKTest::testMultiDocuments()
3239 for (int i = 0; i < 3; i++)
3241 // Load a document.
3242 std::unique_ptr<LibLODocument_Impl> document1 = loadDocImpl("blank_text.odt");
3243 LibLODocument_Impl* pDocument1 = document1.get();
3244 CPPUNIT_ASSERT_EQUAL(1, pDocument1->m_pDocumentClass->getViewsCount(pDocument1));
3245 const int nDocId1 = pDocument1->mnDocumentId;
3247 const int nDoc1View0 = pDocument1->m_pDocumentClass->getView(pDocument1);
3248 CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View0));
3249 const int nDoc1View1 = pDocument1->m_pDocumentClass->createView(pDocument1);
3250 CPPUNIT_ASSERT_EQUAL(nDoc1View1, pDocument1->m_pDocumentClass->getView(pDocument1));
3251 CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View1));
3252 CPPUNIT_ASSERT_EQUAL(2, pDocument1->m_pDocumentClass->getViewsCount(pDocument1));
3254 // Validate the views of document 1.
3255 std::vector<int> aViewIdsDoc1(2);
3256 CPPUNIT_ASSERT(pDocument1->m_pDocumentClass->getViewIds(pDocument1, aViewIdsDoc1.data(), aViewIdsDoc1.size()));
3257 CPPUNIT_ASSERT_EQUAL(nDoc1View0, aViewIdsDoc1[0]);
3258 CPPUNIT_ASSERT_EQUAL(nDoc1View1, aViewIdsDoc1[1]);
3260 CPPUNIT_ASSERT_EQUAL(nDoc1View1, pDocument1->m_pDocumentClass->getView(pDocument1));
3261 CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View1));
3262 pDocument1->m_pDocumentClass->setView(pDocument1, nDoc1View0);
3263 CPPUNIT_ASSERT_EQUAL(nDoc1View0, pDocument1->m_pDocumentClass->getView(pDocument1));
3264 CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View0));
3265 pDocument1->m_pDocumentClass->setView(pDocument1, nDoc1View1);
3266 CPPUNIT_ASSERT_EQUAL(nDoc1View1, pDocument1->m_pDocumentClass->getView(pDocument1));
3267 CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View1));
3268 CPPUNIT_ASSERT_EQUAL(2, pDocument1->m_pDocumentClass->getViewsCount(pDocument1));
3270 // Load another document.
3271 std::unique_ptr<LibLODocument_Impl> document2 = loadDocImpl("blank_presentation.odp");
3272 LibLODocument_Impl* pDocument2 = document2.get();
3273 CPPUNIT_ASSERT_EQUAL(1, pDocument2->m_pDocumentClass->getViewsCount(pDocument2));
3274 const int nDocId2 = pDocument2->mnDocumentId;
3276 const int nDoc2View0 = pDocument2->m_pDocumentClass->getView(pDocument2);
3277 CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View0));
3278 const int nDoc2View1 = pDocument2->m_pDocumentClass->createView(pDocument2);
3279 CPPUNIT_ASSERT_EQUAL(nDoc2View1, pDocument2->m_pDocumentClass->getView(pDocument2));
3280 CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View1));
3281 CPPUNIT_ASSERT_EQUAL(2, pDocument2->m_pDocumentClass->getViewsCount(pDocument2));
3283 // Validate the views of document 2.
3284 std::vector<int> aViewIdsDoc2(2);
3285 CPPUNIT_ASSERT(pDocument2->m_pDocumentClass->getViewIds(pDocument2, aViewIdsDoc2.data(), aViewIdsDoc2.size()));
3286 CPPUNIT_ASSERT_EQUAL(nDoc2View0, aViewIdsDoc2[0]);
3287 CPPUNIT_ASSERT_EQUAL(nDoc2View1, aViewIdsDoc2[1]);
3289 CPPUNIT_ASSERT_EQUAL(nDoc2View1, pDocument2->m_pDocumentClass->getView(pDocument2));
3290 CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View1));
3291 pDocument2->m_pDocumentClass->setView(pDocument2, nDoc2View0);
3292 CPPUNIT_ASSERT_EQUAL(nDoc2View0, pDocument2->m_pDocumentClass->getView(pDocument2));
3293 CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View0));
3294 pDocument2->m_pDocumentClass->setView(pDocument2, nDoc2View1);
3295 CPPUNIT_ASSERT_EQUAL(nDoc2View1, pDocument2->m_pDocumentClass->getView(pDocument2));
3296 CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View1));
3297 CPPUNIT_ASSERT_EQUAL(2, pDocument2->m_pDocumentClass->getViewsCount(pDocument2));
3299 // The views of document1 should be unchanged.
3300 CPPUNIT_ASSERT(pDocument1->m_pDocumentClass->getViewIds(pDocument1, aViewIdsDoc1.data(), aViewIdsDoc1.size()));
3301 CPPUNIT_ASSERT_EQUAL(nDoc1View0, aViewIdsDoc1[0]);
3302 CPPUNIT_ASSERT_EQUAL(nDoc1View1, aViewIdsDoc1[1]);
3303 // Switch views in the first doc.
3304 CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View0));
3305 pDocument1->m_pDocumentClass->setView(pDocument1, nDoc1View0);
3306 CPPUNIT_ASSERT_EQUAL(nDoc1View0, pDocument1->m_pDocumentClass->getView(pDocument1));
3307 CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View1));
3308 pDocument1->m_pDocumentClass->destroyView(pDocument1, nDoc1View1);
3309 CPPUNIT_ASSERT_EQUAL(1, pDocument1->m_pDocumentClass->getViewsCount(pDocument1));
3311 // The views of document2 should be unchanged.
3312 CPPUNIT_ASSERT(pDocument2->m_pDocumentClass->getViewIds(pDocument2, aViewIdsDoc2.data(), aViewIdsDoc2.size()));
3313 CPPUNIT_ASSERT_EQUAL(nDoc2View0, aViewIdsDoc2[0]);
3314 CPPUNIT_ASSERT_EQUAL(nDoc2View1, aViewIdsDoc2[1]);
3315 // Switch views in the second doc.
3316 CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View0));
3317 pDocument2->m_pDocumentClass->setView(pDocument2, nDoc2View0);
3318 CPPUNIT_ASSERT_EQUAL(nDoc2View0, pDocument2->m_pDocumentClass->getView(pDocument2));
3319 CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View1));
3320 pDocument2->m_pDocumentClass->destroyView(pDocument2, nDoc2View1);
3321 CPPUNIT_ASSERT_EQUAL(1, pDocument2->m_pDocumentClass->getViewsCount(pDocument2));
3323 closeDoc(document2);
3325 closeDoc(document1);
3329 void DesktopLOKTest::testControlState()
3331 LibLODocument_Impl* pDocument = loadDoc("search.ods");
3332 pDocument->pClass->postUnoCommand(pDocument, ".uno:StarShapes", nullptr, false);
3333 TestLokCallbackWrapper::InitializeSidebar();
3334 Scheduler::ProcessEventsToIdle();
3336 boost::property_tree::ptree aState;
3337 SfxViewShell* pViewShell = SfxViewShell::Current();
3338 pViewShell->GetViewFrame().GetBindings().Update();
3339 pViewShell->GetViewFrame().GetBindings().QueryControlState(SID_ATTR_TRANSFORM_WIDTH, aState);
3340 CPPUNIT_ASSERT(!aState.empty());
3343 void DesktopLOKTest::testMetricField()
3345 LibLODocument_Impl* pDocument = loadDoc("search.ods");
3346 pDocument->pClass->postUnoCommand(pDocument, ".uno:StarShapes", nullptr, false);
3347 SfxChildWindow* pSideBar = TestLokCallbackWrapper::InitializeSidebar();
3348 Scheduler::ProcessEventsToIdle();
3350 vcl::Window* pWin = pSideBar->GetWindow();
3351 CPPUNIT_ASSERT(pWin);
3353 WindowUIObject aWinUI(pWin);
3354 std::unique_ptr<UIObject> pUIWin(aWinUI.get_child(u"selectwidth"_ustr));
3355 CPPUNIT_ASSERT(pUIWin);
3357 StringMap aMap;
3358 aMap[u"VALUE"_ustr] = "75.06";
3359 pUIWin->execute(u"VALUE"_ustr, aMap);
3361 StringMap aRet = pUIWin->get_state();
3362 CPPUNIT_ASSERT_EQUAL(aMap[u"VALUE"_ustr], aRet[u"Value"_ustr]);
3365 void DesktopLOKTest::testJumpCursor()
3367 comphelper::LibreOfficeKit::setTiledAnnotations(false);
3369 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
3370 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
3372 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'B', 0);
3373 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'o', 0);
3374 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'l', 0);
3375 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'i', 0);
3376 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'v', 0);
3377 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'i', 0);
3378 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0);
3379 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::ESCAPE);
3380 Scheduler::ProcessEventsToIdle();
3382 // There is a cursor jump to (0, 0) due to
3383 // mpOutlinerView->SetOutputArea( PixelToLogic( tools::Rectangle(0,0,1,1) ) );
3384 // when creating a comment
3385 ViewCallback aView1(pDocument);
3387 pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", nullptr, true);
3388 Scheduler::ProcessEventsToIdle();
3390 CPPUNIT_ASSERT(!aView1.m_bZeroCursor);
3392 comphelper::LibreOfficeKit::setTiledAnnotations(true);
3395 void DesktopLOKTest::testRenderSearchResult_WriterNode()
3397 constexpr const bool bDumpBitmap = false;
3399 LibLODocument_Impl* pDocument = loadDoc("SearchIndexResultTest.odt");
3400 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
3402 Scheduler::ProcessEventsToIdle();
3404 unsigned char* pBuffer = nullptr;
3405 OString aPayload =
3406 "<indexing>"
3407 "<paragraph node_type=\"writer\" index=\"19\">ABC</paragraph>"
3408 "</indexing>"_ostr;
3410 int nWidth = 0;
3411 int nHeight = 0;
3412 size_t nByteSize = 0;
3414 bool bResult = pDocument->m_pDocumentClass->renderSearchResult(pDocument, aPayload.getStr(), &pBuffer, &nWidth, &nHeight, &nByteSize);
3416 CPPUNIT_ASSERT(bResult);
3417 CPPUNIT_ASSERT(pBuffer);
3419 Scheduler::ProcessEventsToIdle();
3421 CPPUNIT_ASSERT_EQUAL(642, nWidth);
3422 CPPUNIT_ASSERT_EQUAL(561, nHeight);
3423 CPPUNIT_ASSERT_EQUAL(size_t(1440648), nByteSize);
3425 const sal_uInt8* pD = reinterpret_cast<const sal_uInt8*>(pBuffer);
3426 BitmapEx aBitmap = vcl::bitmap::CreateFromData(pD, nWidth, nHeight, nWidth * 4, /*nBitsPerPixel*/32, true, true);
3428 if (bDumpBitmap)
3430 SvFileStream aStream(u"~/SearchResultBitmap.png"_ustr, StreamMode::WRITE | StreamMode::TRUNC);
3431 vcl::PngImageWriter aPNGWriter(aStream);
3432 aPNGWriter.write(aBitmap);
3434 CPPUNIT_ASSERT_EQUAL(tools::Long(642), aBitmap.GetSizePixel().Width());
3435 CPPUNIT_ASSERT_EQUAL(tools::Long(561), aBitmap.GetSizePixel().Height());
3437 std::free(pBuffer);
3440 void DesktopLOKTest::testRenderSearchResult_CommonNode()
3442 constexpr const bool bDumpBitmap = false;
3444 LibLODocument_Impl* pDocument = loadDoc("SearchIndexResultShapeTest.odt");
3445 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
3447 Scheduler::ProcessEventsToIdle();
3449 unsigned char* pBuffer = nullptr;
3450 OString aPayload =
3451 "<indexing>"
3452 "<paragraph node_type=\"common\" index=\"0\" object_name=\"Shape 1\" />"
3453 "</indexing>"_ostr;
3455 int nWidth = 0;
3456 int nHeight = 0;
3457 size_t nByteSize = 0;
3459 bool bResult = pDocument->m_pDocumentClass->renderSearchResult(pDocument, aPayload.getStr(), &pBuffer, &nWidth, &nHeight, &nByteSize);
3461 CPPUNIT_ASSERT(bResult);
3462 CPPUNIT_ASSERT(pBuffer);
3464 Scheduler::ProcessEventsToIdle();
3466 CPPUNIT_ASSERT_EQUAL(192, nWidth);
3467 CPPUNIT_ASSERT_EQUAL(96, nHeight);
3468 CPPUNIT_ASSERT_EQUAL(size_t(73728), nByteSize);
3470 const sal_uInt8* pD = reinterpret_cast<const sal_uInt8*>(pBuffer);
3471 BitmapEx aBitmap = vcl::bitmap::CreateFromData(pD, nWidth, nHeight, nWidth * 4, /*nBitsPerPixel*/32, true, true);
3473 if (bDumpBitmap)
3475 SvFileStream aStream(u"~/SearchResultBitmap.png"_ustr, StreamMode::WRITE | StreamMode::TRUNC);
3476 vcl::PngImageWriter aPNGWriter(aStream);
3477 aPNGWriter.write(aBitmap);
3479 CPPUNIT_ASSERT_EQUAL(tools::Long(192), aBitmap.GetSizePixel().Width());
3480 CPPUNIT_ASSERT_EQUAL(tools::Long(96), aBitmap.GetSizePixel().Height());
3482 std::free(pBuffer);
3485 static void lcl_repeatKeyStroke(LibLODocument_Impl *pDocument, int nCharCode, int nKeyCode, size_t nCount)
3487 for (size_t nCtr = 0; nCtr < nCount; ++nCtr)
3489 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, nCharCode, nKeyCode);
3490 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, nCharCode, nKeyCode);
3494 void DesktopLOKTest::testNoDuplicateTableSelection()
3496 LibLODocument_Impl* pDocument = loadDoc("table-selection.odt");
3498 // Create view 1.
3499 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
3500 ViewCallback aView1(pDocument);
3502 lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1);
3503 Scheduler::ProcessEventsToIdle();
3504 CPPUNIT_ASSERT_EQUAL(1, aView1.m_nTableSelectionCount);
3505 CPPUNIT_ASSERT(aView1.m_bEmptyTableSelection);
3507 aView1.m_nTableSelectionCount = 0;
3508 // Go to Table1.
3509 lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1);
3510 Scheduler::ProcessEventsToIdle();
3511 CPPUNIT_ASSERT_EQUAL(1, aView1.m_nTableSelectionCount);
3512 CPPUNIT_ASSERT(!aView1.m_bEmptyTableSelection);
3514 aView1.m_nTableSelectionCount = 0;
3515 // Move to the last row in Table1.
3516 lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 2);
3517 Scheduler::ProcessEventsToIdle();
3518 CPPUNIT_ASSERT_EQUAL(0, aView1.m_nTableSelectionCount);
3520 // Go outside Table1.
3521 lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1);
3522 Scheduler::ProcessEventsToIdle();
3523 CPPUNIT_ASSERT_EQUAL(1, aView1.m_nTableSelectionCount);
3524 CPPUNIT_ASSERT(aView1.m_bEmptyTableSelection);
3527 void DesktopLOKTest::testMultiViewTableSelection()
3529 LibLODocument_Impl* pDocument = loadDoc("table-selection.odt");
3531 // Create view 1.
3532 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
3533 ViewCallback aView1(pDocument);
3534 int nView1 = pDocument->m_pDocumentClass->getView(pDocument);
3536 // Create view 2.
3537 pDocument->m_pDocumentClass->createView(pDocument);
3538 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
3539 ViewCallback aView2(pDocument);
3540 int nView2 = pDocument->m_pDocumentClass->getView(pDocument);
3542 // switch to view 1.
3543 pDocument->m_pDocumentClass->setView(pDocument, nView1);
3544 lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1);
3545 Scheduler::ProcessEventsToIdle();
3546 CPPUNIT_ASSERT_EQUAL(1, aView1.m_nTableSelectionCount);
3547 CPPUNIT_ASSERT_EQUAL(1, aView2.m_nTableSelectionCount);
3548 CPPUNIT_ASSERT(aView1.m_bEmptyTableSelection);
3549 CPPUNIT_ASSERT(aView2.m_bEmptyTableSelection);
3551 aView1.m_nTableSelectionCount = 0;
3552 aView2.m_nTableSelectionCount = 0;
3554 pDocument->m_pDocumentClass->setView(pDocument, nView1);
3555 // Go to Table1.
3556 lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1);
3557 Scheduler::ProcessEventsToIdle();
3558 CPPUNIT_ASSERT_EQUAL(1, aView1.m_nTableSelectionCount);
3559 CPPUNIT_ASSERT_EQUAL(0, aView2.m_nTableSelectionCount);
3561 aView1.m_nTableSelectionCount = 0;
3562 // Switch to view 2
3563 pDocument->m_pDocumentClass->setView(pDocument, nView2);
3564 // Go to Table2 in view 2.
3565 lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 7);
3566 Scheduler::ProcessEventsToIdle();
3567 // View1 should not get any table selection messages.
3568 CPPUNIT_ASSERT_EQUAL(0, aView1.m_nTableSelectionCount);
3569 // View2 will first get table selection of Table1, then empty selection, and finally on 7th down arrow keypress,
3570 // it will get table-selection of Table2. So in total it should get 3 table selections.
3571 CPPUNIT_ASSERT_EQUAL(3, aView2.m_nTableSelectionCount);
3572 CPPUNIT_ASSERT(!aView2.m_bEmptyTableSelection);
3574 aView1.m_nTableSelectionCount = 0;
3575 aView2.m_nTableSelectionCount = 0;
3577 // Switch to view 1
3578 pDocument->m_pDocumentClass->setView(pDocument, nView1);
3579 // Go out of Table1 and re-enter..
3580 lcl_repeatKeyStroke(pDocument, 0, KEY_UP, 1);
3581 lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1);
3582 Scheduler::ProcessEventsToIdle();
3583 // View1 should get one empty table selection, then get Table1 selection.
3584 CPPUNIT_ASSERT_EQUAL(2, aView1.m_nTableSelectionCount);
3585 // View2 should not get any table selection.
3586 CPPUNIT_ASSERT_EQUAL(0, aView2.m_nTableSelectionCount);
3587 CPPUNIT_ASSERT(!aView1.m_bEmptyTableSelection);
3590 void DesktopLOKTest::testColorPaletteCallback()
3592 LibLODocument_Impl* pDocument = loadDoc("ThemeDocument.docx");
3594 // Create view 1.
3595 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
3596 ViewCallback aView1(pDocument);
3597 Scheduler::ProcessEventsToIdle();
3599 CPPUNIT_ASSERT_EQUAL(1, aView1.m_nColorPaletteCallbackCount);
3600 boost::property_tree::ptree aValues = aView1.m_aColorPaletteCallbackResult.get_child("ThemeColors");
3601 CPPUNIT_ASSERT(!aValues.empty());
3602 CPPUNIT_ASSERT_EQUAL(size_t(6), aValues.size());
3605 // Create view 2.
3606 pDocument->m_pDocumentClass->createView(pDocument);
3607 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
3608 ViewCallback aView2(pDocument);
3609 Scheduler::ProcessEventsToIdle();
3611 CPPUNIT_ASSERT_EQUAL(1, aView2.m_nColorPaletteCallbackCount);
3612 boost::property_tree::ptree aValues = aView1.m_aColorPaletteCallbackResult.get_child("ThemeColors");
3613 CPPUNIT_ASSERT(!aValues.empty());
3614 CPPUNIT_ASSERT_EQUAL(size_t(6), aValues.size());
3618 namespace {
3620 constexpr size_t classOffset(int i)
3622 return sizeof(static_cast<struct _LibreOfficeKitClass*>(nullptr)->nSize) + i * sizeof(void*);
3625 constexpr size_t documentClassOffset(int i)
3627 return sizeof(static_cast<struct _LibreOfficeKitDocumentClass*>(nullptr)->nSize) + i * sizeof(void*);
3632 void DesktopLOKTest::testABI()
3634 // STABLE ABI, NEVER CHANGE (unless there's a very good reason, agreed by ESC, etc.)
3635 CPPUNIT_ASSERT_EQUAL(classOffset(0), offsetof(struct _LibreOfficeKitClass, destroy));
3636 CPPUNIT_ASSERT_EQUAL(classOffset(1), offsetof(struct _LibreOfficeKitClass, documentLoad));
3637 CPPUNIT_ASSERT_EQUAL(classOffset(2), offsetof(struct _LibreOfficeKitClass, getError));
3638 CPPUNIT_ASSERT_EQUAL(classOffset(3), offsetof(struct _LibreOfficeKitClass, documentLoadWithOptions));
3639 CPPUNIT_ASSERT_EQUAL(classOffset(4), offsetof(struct _LibreOfficeKitClass, freeError));
3640 CPPUNIT_ASSERT_EQUAL(classOffset(5), offsetof(struct _LibreOfficeKitClass, registerCallback));
3641 CPPUNIT_ASSERT_EQUAL(classOffset(6), offsetof(struct _LibreOfficeKitClass, getFilterTypes));
3642 CPPUNIT_ASSERT_EQUAL(classOffset(7), offsetof(struct _LibreOfficeKitClass, setOptionalFeatures));
3643 CPPUNIT_ASSERT_EQUAL(classOffset(8), offsetof(struct _LibreOfficeKitClass, setDocumentPassword));
3644 CPPUNIT_ASSERT_EQUAL(classOffset(9), offsetof(struct _LibreOfficeKitClass, getVersionInfo));
3645 CPPUNIT_ASSERT_EQUAL(classOffset(10), offsetof(struct _LibreOfficeKitClass, runMacro));
3646 CPPUNIT_ASSERT_EQUAL(classOffset(11), offsetof(struct _LibreOfficeKitClass, signDocument));
3647 CPPUNIT_ASSERT_EQUAL(classOffset(12), offsetof(struct _LibreOfficeKitClass, runLoop));
3648 CPPUNIT_ASSERT_EQUAL(classOffset(13), offsetof(struct _LibreOfficeKitClass, sendDialogEvent));
3649 CPPUNIT_ASSERT_EQUAL(classOffset(14), offsetof(struct _LibreOfficeKitClass, setOption));
3650 CPPUNIT_ASSERT_EQUAL(classOffset(15), offsetof(struct _LibreOfficeKitClass, dumpState));
3651 CPPUNIT_ASSERT_EQUAL(classOffset(16), offsetof(struct _LibreOfficeKitClass, extractRequest));
3652 CPPUNIT_ASSERT_EQUAL(classOffset(17), offsetof(struct _LibreOfficeKitClass, trimMemory));
3653 CPPUNIT_ASSERT_EQUAL(classOffset(18), offsetof(struct _LibreOfficeKitClass, startURP));
3654 CPPUNIT_ASSERT_EQUAL(classOffset(19), offsetof(struct _LibreOfficeKitClass, stopURP));
3655 CPPUNIT_ASSERT_EQUAL(classOffset(20), offsetof(struct _LibreOfficeKitClass, joinThreads));
3656 CPPUNIT_ASSERT_EQUAL(classOffset(21), offsetof(struct _LibreOfficeKitClass, startThreads));
3657 CPPUNIT_ASSERT_EQUAL(classOffset(22), offsetof(struct _LibreOfficeKitClass, setForkedChild));
3658 CPPUNIT_ASSERT_EQUAL(classOffset(23), offsetof(struct _LibreOfficeKitClass, extractDocumentStructureRequest));
3659 CPPUNIT_ASSERT_EQUAL(classOffset(24), offsetof(struct _LibreOfficeKitClass, registerAnyInputCallback));
3661 // When extending LibreOfficeKit with a new function pointer, add new assert for the offsetof the
3662 // new function pointer and bump this assert for the size of the class.
3663 CPPUNIT_ASSERT_EQUAL(classOffset(25), sizeof(struct _LibreOfficeKitClass));
3665 CPPUNIT_ASSERT_EQUAL(documentClassOffset(0), offsetof(struct _LibreOfficeKitDocumentClass, destroy));
3666 CPPUNIT_ASSERT_EQUAL(documentClassOffset(1), offsetof(struct _LibreOfficeKitDocumentClass, saveAs));
3668 // Unstable ABI, but still think twice before changing this
3669 // Eg. can't you add your new member at the end of the struct instead of
3670 // in the middle? The thing you are changing - is it already part of some
3671 // release?
3672 CPPUNIT_ASSERT_EQUAL(documentClassOffset(2), offsetof(struct _LibreOfficeKitDocumentClass, getDocumentType));
3673 CPPUNIT_ASSERT_EQUAL(documentClassOffset(3), offsetof(struct _LibreOfficeKitDocumentClass, getParts));
3674 CPPUNIT_ASSERT_EQUAL(documentClassOffset(4), offsetof(struct _LibreOfficeKitDocumentClass, getPartPageRectangles));
3675 CPPUNIT_ASSERT_EQUAL(documentClassOffset(5), offsetof(struct _LibreOfficeKitDocumentClass, getPart));
3676 CPPUNIT_ASSERT_EQUAL(documentClassOffset(6), offsetof(struct _LibreOfficeKitDocumentClass, setPart));
3677 CPPUNIT_ASSERT_EQUAL(documentClassOffset(7), offsetof(struct _LibreOfficeKitDocumentClass, getPartName));
3678 CPPUNIT_ASSERT_EQUAL(documentClassOffset(8), offsetof(struct _LibreOfficeKitDocumentClass, setPartMode));
3679 CPPUNIT_ASSERT_EQUAL(documentClassOffset(9), offsetof(struct _LibreOfficeKitDocumentClass, paintTile));
3680 CPPUNIT_ASSERT_EQUAL(documentClassOffset(10), offsetof(struct _LibreOfficeKitDocumentClass, getTileMode));
3681 CPPUNIT_ASSERT_EQUAL(documentClassOffset(11), offsetof(struct _LibreOfficeKitDocumentClass, getDocumentSize));
3682 CPPUNIT_ASSERT_EQUAL(documentClassOffset(12), offsetof(struct _LibreOfficeKitDocumentClass, initializeForRendering));
3683 CPPUNIT_ASSERT_EQUAL(documentClassOffset(13), offsetof(struct _LibreOfficeKitDocumentClass, registerCallback));
3684 CPPUNIT_ASSERT_EQUAL(documentClassOffset(14), offsetof(struct _LibreOfficeKitDocumentClass, postKeyEvent));
3685 CPPUNIT_ASSERT_EQUAL(documentClassOffset(15), offsetof(struct _LibreOfficeKitDocumentClass, postMouseEvent));
3686 CPPUNIT_ASSERT_EQUAL(documentClassOffset(16), offsetof(struct _LibreOfficeKitDocumentClass, postUnoCommand));
3687 CPPUNIT_ASSERT_EQUAL(documentClassOffset(17), offsetof(struct _LibreOfficeKitDocumentClass, setTextSelection));
3688 CPPUNIT_ASSERT_EQUAL(documentClassOffset(18), offsetof(struct _LibreOfficeKitDocumentClass, getTextSelection));
3689 CPPUNIT_ASSERT_EQUAL(documentClassOffset(19), offsetof(struct _LibreOfficeKitDocumentClass, paste));
3690 CPPUNIT_ASSERT_EQUAL(documentClassOffset(20), offsetof(struct _LibreOfficeKitDocumentClass, setGraphicSelection));
3691 CPPUNIT_ASSERT_EQUAL(documentClassOffset(21), offsetof(struct _LibreOfficeKitDocumentClass, resetSelection));
3692 CPPUNIT_ASSERT_EQUAL(documentClassOffset(22), offsetof(struct _LibreOfficeKitDocumentClass, getCommandValues));
3693 CPPUNIT_ASSERT_EQUAL(documentClassOffset(23), offsetof(struct _LibreOfficeKitDocumentClass, setClientZoom));
3694 CPPUNIT_ASSERT_EQUAL(documentClassOffset(24), offsetof(struct _LibreOfficeKitDocumentClass, setClientVisibleArea));
3695 CPPUNIT_ASSERT_EQUAL(documentClassOffset(25), offsetof(struct _LibreOfficeKitDocumentClass, createView));
3696 CPPUNIT_ASSERT_EQUAL(documentClassOffset(26), offsetof(struct _LibreOfficeKitDocumentClass, destroyView));
3697 CPPUNIT_ASSERT_EQUAL(documentClassOffset(27), offsetof(struct _LibreOfficeKitDocumentClass, setView));
3698 CPPUNIT_ASSERT_EQUAL(documentClassOffset(28), offsetof(struct _LibreOfficeKitDocumentClass, getView));
3699 CPPUNIT_ASSERT_EQUAL(documentClassOffset(29), offsetof(struct _LibreOfficeKitDocumentClass, getViewsCount));
3700 CPPUNIT_ASSERT_EQUAL(documentClassOffset(30), offsetof(struct _LibreOfficeKitDocumentClass, renderFont));
3701 CPPUNIT_ASSERT_EQUAL(documentClassOffset(31), offsetof(struct _LibreOfficeKitDocumentClass, getPartHash));
3702 CPPUNIT_ASSERT_EQUAL(documentClassOffset(32), offsetof(struct _LibreOfficeKitDocumentClass, paintPartTile));
3703 CPPUNIT_ASSERT_EQUAL(documentClassOffset(33), offsetof(struct _LibreOfficeKitDocumentClass, getViewIds));
3704 CPPUNIT_ASSERT_EQUAL(documentClassOffset(34), offsetof(struct _LibreOfficeKitDocumentClass, setOutlineState));
3705 CPPUNIT_ASSERT_EQUAL(documentClassOffset(35), offsetof(struct _LibreOfficeKitDocumentClass, paintWindow));
3706 CPPUNIT_ASSERT_EQUAL(documentClassOffset(36), offsetof(struct _LibreOfficeKitDocumentClass, postWindow));
3707 CPPUNIT_ASSERT_EQUAL(documentClassOffset(37), offsetof(struct _LibreOfficeKitDocumentClass, postWindowKeyEvent));
3708 CPPUNIT_ASSERT_EQUAL(documentClassOffset(38), offsetof(struct _LibreOfficeKitDocumentClass, postWindowMouseEvent));
3709 CPPUNIT_ASSERT_EQUAL(documentClassOffset(39), offsetof(struct _LibreOfficeKitDocumentClass, setViewLanguage));
3710 CPPUNIT_ASSERT_EQUAL(documentClassOffset(40), offsetof(struct _LibreOfficeKitDocumentClass, postWindowExtTextInputEvent));
3711 CPPUNIT_ASSERT_EQUAL(documentClassOffset(41), offsetof(struct _LibreOfficeKitDocumentClass, getPartInfo));
3712 CPPUNIT_ASSERT_EQUAL(documentClassOffset(42), offsetof(struct _LibreOfficeKitDocumentClass, paintWindowDPI));
3713 CPPUNIT_ASSERT_EQUAL(documentClassOffset(43), offsetof(struct _LibreOfficeKitDocumentClass, insertCertificate));
3714 CPPUNIT_ASSERT_EQUAL(documentClassOffset(44), offsetof(struct _LibreOfficeKitDocumentClass, addCertificate));
3715 CPPUNIT_ASSERT_EQUAL(documentClassOffset(45), offsetof(struct _LibreOfficeKitDocumentClass, getSignatureState));
3716 CPPUNIT_ASSERT_EQUAL(documentClassOffset(46), offsetof(struct _LibreOfficeKitDocumentClass, renderShapeSelection));
3717 CPPUNIT_ASSERT_EQUAL(documentClassOffset(47), offsetof(struct _LibreOfficeKitDocumentClass, postWindowGestureEvent));
3718 CPPUNIT_ASSERT_EQUAL(documentClassOffset(48), offsetof(struct _LibreOfficeKitDocumentClass, createViewWithOptions));
3719 CPPUNIT_ASSERT_EQUAL(documentClassOffset(49), offsetof(struct _LibreOfficeKitDocumentClass, selectPart));
3720 CPPUNIT_ASSERT_EQUAL(documentClassOffset(50), offsetof(struct _LibreOfficeKitDocumentClass, moveSelectedParts));
3721 CPPUNIT_ASSERT_EQUAL(documentClassOffset(51), offsetof(struct _LibreOfficeKitDocumentClass, resizeWindow));
3722 CPPUNIT_ASSERT_EQUAL(documentClassOffset(52), offsetof(struct _LibreOfficeKitDocumentClass, getClipboard));
3723 CPPUNIT_ASSERT_EQUAL(documentClassOffset(53), offsetof(struct _LibreOfficeKitDocumentClass, setClipboard));
3724 CPPUNIT_ASSERT_EQUAL(documentClassOffset(54), offsetof(struct _LibreOfficeKitDocumentClass, getSelectionType));
3725 CPPUNIT_ASSERT_EQUAL(documentClassOffset(55), offsetof(struct _LibreOfficeKitDocumentClass, removeTextContext));
3726 CPPUNIT_ASSERT_EQUAL(documentClassOffset(56), offsetof(struct _LibreOfficeKitDocumentClass, sendDialogEvent));
3727 CPPUNIT_ASSERT_EQUAL(documentClassOffset(57), offsetof(struct _LibreOfficeKitDocumentClass, renderFontOrientation));
3728 CPPUNIT_ASSERT_EQUAL(documentClassOffset(58), offsetof(struct _LibreOfficeKitDocumentClass, paintWindowForView));
3729 CPPUNIT_ASSERT_EQUAL(documentClassOffset(59), offsetof(struct _LibreOfficeKitDocumentClass, completeFunction));
3730 CPPUNIT_ASSERT_EQUAL(documentClassOffset(60), offsetof(struct _LibreOfficeKitDocumentClass, setWindowTextSelection));
3731 CPPUNIT_ASSERT_EQUAL(documentClassOffset(61), offsetof(struct _LibreOfficeKitDocumentClass, sendFormFieldEvent));
3732 CPPUNIT_ASSERT_EQUAL(documentClassOffset(62), offsetof(struct _LibreOfficeKitDocumentClass, setBlockedCommandList));
3733 CPPUNIT_ASSERT_EQUAL(documentClassOffset(63), offsetof(struct _LibreOfficeKitDocumentClass, renderSearchResult));
3734 CPPUNIT_ASSERT_EQUAL(documentClassOffset(64), offsetof(struct _LibreOfficeKitDocumentClass, sendContentControlEvent));
3735 CPPUNIT_ASSERT_EQUAL(documentClassOffset(65), offsetof(struct _LibreOfficeKitDocumentClass, getSelectionTypeAndText));
3736 CPPUNIT_ASSERT_EQUAL(documentClassOffset(66), offsetof(struct _LibreOfficeKitDocumentClass, getDataArea));
3737 CPPUNIT_ASSERT_EQUAL(documentClassOffset(67), offsetof(struct _LibreOfficeKitDocumentClass, getEditMode));
3738 CPPUNIT_ASSERT_EQUAL(documentClassOffset(68), offsetof(struct _LibreOfficeKitDocumentClass, setViewTimezone));
3739 CPPUNIT_ASSERT_EQUAL(documentClassOffset(69), offsetof(struct _LibreOfficeKitDocumentClass, setAccessibilityState));
3740 CPPUNIT_ASSERT_EQUAL(documentClassOffset(70), offsetof(struct _LibreOfficeKitDocumentClass, getA11yFocusedParagraph));
3741 CPPUNIT_ASSERT_EQUAL(documentClassOffset(71), offsetof(struct _LibreOfficeKitDocumentClass, getA11yCaretPosition));
3742 CPPUNIT_ASSERT_EQUAL(documentClassOffset(72), offsetof(struct _LibreOfficeKitDocumentClass, setViewReadOnly));
3743 CPPUNIT_ASSERT_EQUAL(documentClassOffset(73), offsetof(struct _LibreOfficeKitDocumentClass, setAllowChangeComments));
3744 CPPUNIT_ASSERT_EQUAL(documentClassOffset(74), offsetof(struct _LibreOfficeKitDocumentClass, getPresentationInfo));
3745 CPPUNIT_ASSERT_EQUAL(documentClassOffset(75), offsetof(struct _LibreOfficeKitDocumentClass, createSlideRenderer));
3746 CPPUNIT_ASSERT_EQUAL(documentClassOffset(76), offsetof(struct _LibreOfficeKitDocumentClass, postSlideshowCleanup));
3747 CPPUNIT_ASSERT_EQUAL(documentClassOffset(77), offsetof(struct _LibreOfficeKitDocumentClass, renderNextSlideLayer));
3748 CPPUNIT_ASSERT_EQUAL(documentClassOffset(78), offsetof(struct _LibreOfficeKitDocumentClass, setViewOption));
3750 // As above
3751 CPPUNIT_ASSERT_EQUAL(documentClassOffset(79), sizeof(struct _LibreOfficeKitDocumentClass));
3754 CPPUNIT_TEST_SUITE_REGISTRATION(DesktopLOKTest);
3756 CPPUNIT_PLUGIN_IMPLEMENT();
3758 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */