lok: vcl: fix multiple floatwin removal case more robustly.
[LibreOffice.git] / desktop / qa / desktop_lib / test_desktop_lib.cxx
blobf819443473d7f82eaf1a205466de296a13f67247
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 <memory>
11 #include <com/sun/star/frame/Desktop.hpp>
12 #include <com/sun/star/lang/XComponent.hpp>
13 #include <com/sun/star/text/XTextDocument.hpp>
14 #include <com/sun/star/awt/Key.hpp>
15 #include <com/sun/star/awt/XReschedule.hpp>
16 #include <com/sun/star/awt/Toolkit.hpp>
17 #include <com/sun/star/drawing/XDrawPageSupplier.hpp>
18 #include <com/sun/star/util/XModifiable.hpp>
19 #include <com/sun/star/text/TextContentAnchorType.hpp>
20 #include <boost/property_tree/json_parser.hpp>
22 #include <com/sun/star/frame/XStorable.hpp>
24 #include <vcl/combobox.hxx>
25 #include <vcl/scheduler.hxx>
26 #include <vcl/svapp.hxx>
27 #include <vcl/syswin.hxx>
28 #include <vcl/window.hxx>
29 #include <comphelper/processfactory.hxx>
30 #include <rtl/uri.hxx>
31 #include <sfx2/objsh.hxx>
32 #include <sfx2/lokhelper.hxx>
33 #include <test/unoapi_test.hxx>
34 #include <comphelper/lok.hxx>
35 #include <comphelper/dispatchcommand.hxx>
36 #include <comphelper/propertysequence.hxx>
37 #include <osl/conditn.hxx>
38 #include <svl/srchitem.hxx>
39 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
40 #include <unotools/tempfile.hxx>
41 #include <sfx2/viewsh.hxx>
42 #include <sfx2/viewfrm.hxx>
43 #include <sfx2/bindings.hxx>
44 #include <unotools/datetime.hxx>
45 #include <unotools/syslocaleoptions.hxx>
46 #include <comphelper/string.hxx>
47 #include <comphelper/scopeguard.hxx>
48 #include <cairo.h>
49 #include <ostream>
50 #include <config_features.h>
52 #include <lib/init.hxx>
54 using namespace com::sun::star;
55 using namespace desktop;
57 class DesktopLOKTest : public UnoApiTest
59 class Resetter
61 private:
62 std::function<void ()> m_Func;
64 public:
65 Resetter(std::function<void ()> const& rFunc)
66 : m_Func(rFunc)
69 ~Resetter()
71 try
73 m_Func();
75 catch (...) // has to be reliable
77 fprintf(stderr, "resetter failed with exception\n");
78 abort();
83 public:
84 DesktopLOKTest() : UnoApiTest("/desktop/qa/data/"),
85 m_nSelectionBeforeSearchResult(0),
86 m_nSelectionAfterSearchResult(0),
87 m_bModified(false),
88 m_nTrackChanges(0)
92 void readFileIntoByteVector(OUString const & sFilename, std::vector<sal_uInt8> & rByteVector);
94 virtual void setUp() override
96 comphelper::LibreOfficeKit::setActive(true);
98 UnoApiTest::setUp();
100 mxDesktop.set(frame::Desktop::create(comphelper::getComponentContext(getMultiServiceFactory())));
101 SfxApplication::GetOrCreate();
104 virtual void tearDown() override
106 if (m_pDocument)
107 m_pDocument->pClass->registerCallback(m_pDocument.get(), nullptr, nullptr);
108 closeDoc();
110 UnoApiTest::tearDown();
112 comphelper::LibreOfficeKit::setActive(false);
115 LibLODocument_Impl* loadDocUrl(const OUString& rFileURL, LibreOfficeKitDocumentType eType);
116 LibLODocument_Impl* loadDoc(const char* pName, LibreOfficeKitDocumentType eType = LOK_DOCTYPE_TEXT);
117 void closeDoc();
118 static void callback(int nType, const char* pPayload, void* pData);
119 void callbackImpl(int nType, const char* pPayload);
121 void testGetStyles();
122 void testGetFonts();
123 void testCreateView();
124 void testGetFilterTypes();
125 void testGetPartPageRectangles();
126 void testSearchCalc();
127 void testSearchAllNotificationsCalc();
128 void testPaintTile();
129 void testSaveAs();
130 void testSaveAsCalc();
131 void testPasteWriter();
132 void testPasteWriterJPEG();
133 void testUndoWriter();
134 void testRowColumnHeaders();
135 void testHiddenRowHeaders();
136 void testCellCursor();
137 void testCommandResult();
138 void testWriterComments();
139 void testSheetOperations();
140 void testSheetSelections();
141 void testContextMenuCalc();
142 void testContextMenuWriter();
143 void testContextMenuImpress();
144 void testNotificationCompression();
145 void testTileInvalidationCompression();
146 void testPartInInvalidation();
147 void testInput();
148 void testRedlineWriter();
149 void testTrackChanges();
150 void testRedlineCalc();
151 void testPaintPartTile();
152 void testWriterCommentInsertCursor();
153 #if HAVE_MORE_FONTS
154 void testGetFontSubset();
155 #endif
156 void testCommentsWriter();
157 void testCommentsCalc();
158 void testCommentsImpress();
159 void testCommentsCallbacksWriter();
160 void testRunMacro();
161 void testExtractParameter();
162 void testGetSignatureState_NonSigned();
163 void testGetSignatureState_Signed();
164 void testInsertCertificate_DER_ODT();
165 void testInsertCertificate_PEM_ODT();
166 void testInsertCertificate_PEM_DOCX();
167 void testSignDocument_PEM_PDF();
168 void testTextSelectionHandles();
169 void testComplexSelection();
170 void testSpellcheckerMultiView();
171 void testDialogPaste();
172 void testCalcSaveAs();
173 void testDialogInput();
174 void testABI();
176 CPPUNIT_TEST_SUITE(DesktopLOKTest);
177 CPPUNIT_TEST(testGetStyles);
178 CPPUNIT_TEST(testGetFonts);
179 CPPUNIT_TEST(testCreateView);
180 CPPUNIT_TEST(testGetFilterTypes);
181 CPPUNIT_TEST(testGetPartPageRectangles);
182 CPPUNIT_TEST(testSearchCalc);
183 CPPUNIT_TEST(testSearchAllNotificationsCalc);
184 CPPUNIT_TEST(testPaintTile);
185 CPPUNIT_TEST(testSaveAs);
186 CPPUNIT_TEST(testSaveAsCalc);
187 CPPUNIT_TEST(testPasteWriter);
188 CPPUNIT_TEST(testPasteWriterJPEG);
189 CPPUNIT_TEST(testUndoWriter);
190 CPPUNIT_TEST(testRowColumnHeaders);
191 CPPUNIT_TEST(testHiddenRowHeaders);
192 CPPUNIT_TEST(testCellCursor);
193 CPPUNIT_TEST(testCommandResult);
194 CPPUNIT_TEST(testWriterComments);
195 CPPUNIT_TEST(testSheetOperations);
196 CPPUNIT_TEST(testSheetSelections);
197 CPPUNIT_TEST(testContextMenuCalc);
198 CPPUNIT_TEST(testContextMenuWriter);
199 CPPUNIT_TEST(testContextMenuImpress);
200 CPPUNIT_TEST(testNotificationCompression);
201 CPPUNIT_TEST(testTileInvalidationCompression);
202 CPPUNIT_TEST(testPartInInvalidation);
203 CPPUNIT_TEST(testInput);
204 CPPUNIT_TEST(testRedlineWriter);
205 CPPUNIT_TEST(testTrackChanges);
206 CPPUNIT_TEST(testRedlineCalc);
207 CPPUNIT_TEST(testPaintPartTile);
208 CPPUNIT_TEST(testWriterCommentInsertCursor);
209 #if HAVE_MORE_FONTS
210 CPPUNIT_TEST(testGetFontSubset);
211 #endif
212 CPPUNIT_TEST(testCommentsWriter);
213 CPPUNIT_TEST(testCommentsCalc);
214 CPPUNIT_TEST(testCommentsImpress);
215 CPPUNIT_TEST(testCommentsCallbacksWriter);
216 CPPUNIT_TEST(testRunMacro);
217 CPPUNIT_TEST(testExtractParameter);
218 CPPUNIT_TEST(testGetSignatureState_Signed);
219 CPPUNIT_TEST(testGetSignatureState_NonSigned);
220 CPPUNIT_TEST(testInsertCertificate_DER_ODT);
221 CPPUNIT_TEST(testInsertCertificate_PEM_ODT);
222 CPPUNIT_TEST(testInsertCertificate_PEM_DOCX);
223 CPPUNIT_TEST(testSignDocument_PEM_PDF);
224 CPPUNIT_TEST(testTextSelectionHandles);
225 CPPUNIT_TEST(testComplexSelection);
226 CPPUNIT_TEST(testSpellcheckerMultiView);
227 CPPUNIT_TEST(testDialogPaste);
228 CPPUNIT_TEST(testCalcSaveAs);
229 CPPUNIT_TEST(testDialogInput);
230 CPPUNIT_TEST(testABI);
231 CPPUNIT_TEST_SUITE_END();
233 uno::Reference<lang::XComponent> mxComponent;
234 OString m_aTextSelection;
235 OString m_aTextSelectionStart;
236 OString m_aTextSelectionEnd;
237 std::vector<OString> m_aSearchResultSelection;
238 std::vector<int> m_aSearchResultPart;
239 int m_nSelectionBeforeSearchResult;
240 int m_nSelectionAfterSearchResult;
242 // for testCommandResult
243 osl::Condition m_aCommandResultCondition;
244 OString m_aCommandResult;
246 // for testModifiedStatus
247 osl::Condition m_aStateChangedCondition;
248 bool m_bModified;
249 int m_nTrackChanges;
251 // for testContextMenu{Calc, Writer}
252 osl::Condition m_aContextMenuCondition;
253 boost::property_tree::ptree m_aContextMenuResult;
255 std::unique_ptr<LibLODocument_Impl> m_pDocument;
258 static Control* GetFocusControl(vcl::Window const * pParent)
260 sal_uInt16 nChildren = pParent->GetChildCount();
261 for (sal_uInt16 nChild = 0; nChild < nChildren; ++nChild)
263 vcl::Window* pChild = pParent->GetChild( nChild );
264 Control* pCtrl = dynamic_cast<Control*>(pChild);
265 if (pCtrl && pCtrl->HasControlFocus())
266 return pCtrl;
268 Control* pSubCtrl = GetFocusControl( pChild );
269 if (pSubCtrl)
270 return pSubCtrl;
272 return nullptr;
275 LibLODocument_Impl* DesktopLOKTest::loadDocUrl(const OUString& rFileURL, LibreOfficeKitDocumentType eType)
277 OUString aService;
278 switch (eType)
280 case LOK_DOCTYPE_TEXT:
281 aService = "com.sun.star.text.TextDocument";
282 break;
283 case LOK_DOCTYPE_SPREADSHEET:
284 aService = "com.sun.star.sheet.SpreadsheetDocument";
285 break;
286 case LOK_DOCTYPE_PRESENTATION:
287 aService = "com.sun.star.presentation.PresentationDocument";
288 break;
289 default:
290 CPPUNIT_ASSERT(false);
291 break;
293 mxComponent = loadFromDesktop(rFileURL, aService);
294 if (!mxComponent.is())
296 CPPUNIT_ASSERT(false);
298 m_pDocument.reset(new LibLODocument_Impl(mxComponent));
299 return m_pDocument.get();
302 LibLODocument_Impl* DesktopLOKTest::loadDoc(const char* pName, LibreOfficeKitDocumentType eType)
304 OUString aFileURL;
305 createFileURL(OUString::createFromAscii(pName), aFileURL);
306 return loadDocUrl(aFileURL, eType);
309 void DesktopLOKTest::closeDoc()
311 if (mxComponent.is())
313 closeDocument(mxComponent);
314 mxComponent.clear();
318 void DesktopLOKTest::callback(int nType, const char* pPayload, void* pData)
320 static_cast<DesktopLOKTest*>(pData)->callbackImpl(nType, pPayload);
323 void DesktopLOKTest::callbackImpl(int nType, const char* pPayload)
325 switch (nType)
327 case LOK_CALLBACK_TEXT_SELECTION:
329 m_aTextSelection = pPayload;
330 if (m_aSearchResultSelection.empty())
331 ++m_nSelectionBeforeSearchResult;
332 else
333 ++m_nSelectionAfterSearchResult;
335 break;
336 case LOK_CALLBACK_TEXT_SELECTION_START:
337 m_aTextSelectionStart = pPayload;
338 break;
339 case LOK_CALLBACK_TEXT_SELECTION_END:
340 m_aTextSelectionEnd = pPayload;
341 break;
342 case LOK_CALLBACK_SEARCH_RESULT_SELECTION:
344 m_aSearchResultSelection.clear();
345 boost::property_tree::ptree aTree;
346 std::stringstream aStream(pPayload);
347 boost::property_tree::read_json(aStream, aTree);
348 for (boost::property_tree::ptree::value_type& rValue : aTree.get_child("searchResultSelection"))
350 m_aSearchResultSelection.emplace_back(rValue.second.get<std::string>("rectangles").c_str());
351 m_aSearchResultPart.push_back(std::atoi(rValue.second.get<std::string>("part").c_str()));
354 break;
355 case LOK_CALLBACK_UNO_COMMAND_RESULT:
357 m_aCommandResult = pPayload;
358 m_aCommandResultCondition.set();
360 break;
361 case LOK_CALLBACK_STATE_CHANGED:
363 OString aPayload(pPayload);
364 OString aPrefix(".uno:ModifiedStatus=");
365 if (aPayload.startsWith(aPrefix))
367 m_bModified = aPayload.copy(aPrefix.getLength()).toBoolean();
368 m_aStateChangedCondition.set();
370 else if (aPayload.startsWith(".uno:TrackChanges=") && aPayload.endsWith("=true"))
371 ++m_nTrackChanges;
373 break;
374 case LOK_CALLBACK_CONTEXT_MENU:
376 m_aContextMenuResult.clear();
377 std::stringstream aStream(pPayload);
378 boost::property_tree::read_json(aStream, m_aContextMenuResult);
379 m_aContextMenuCondition.set();
381 break;
385 void DesktopLOKTest::testGetStyles()
387 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
388 boost::property_tree::ptree aTree;
389 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:StyleApply");
390 std::stringstream aStream(pJSON);
391 boost::property_tree::read_json(aStream, aTree);
392 CPPUNIT_ASSERT( !aTree.empty() );
393 CPPUNIT_ASSERT_EQUAL( std::string(".uno:StyleApply"), aTree.get_child("commandName").get_value<std::string>() );
395 boost::property_tree::ptree aValues = aTree.get_child("commandValues");
396 CPPUNIT_ASSERT( !aValues.empty() );
397 for (const auto& rPair : aValues)
399 if( rPair.first != "ClearStyle")
401 CPPUNIT_ASSERT( !rPair.second.empty());
403 if (rPair.first != "CharacterStyles" &&
404 rPair.first != "ParagraphStyles" &&
405 rPair.first != "FrameStyles" &&
406 rPair.first != "PageStyles" &&
407 rPair.first != "NumberingStyles" &&
408 rPair.first != "CellStyles" &&
409 rPair.first != "ShapeStyles" &&
410 rPair.first != "TableStyles" &&
411 rPair.first != "HeaderFooter" &&
412 rPair.first != "Commands")
414 CPPUNIT_FAIL("Unknown style family: " + rPair.first);
419 void DesktopLOKTest::testGetFonts()
421 LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp");
422 boost::property_tree::ptree aTree;
423 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:CharFontName");
424 std::stringstream aStream(pJSON);
425 boost::property_tree::read_json(aStream, aTree);
426 CPPUNIT_ASSERT( !aTree.empty() );
427 CPPUNIT_ASSERT_EQUAL( std::string(".uno:CharFontName"), aTree.get_child("commandName").get_value<std::string>() );
429 boost::property_tree::ptree aValues = aTree.get_child("commandValues");
430 CPPUNIT_ASSERT( !aValues.empty() );
431 for (const auto& rPair : aValues)
433 // check that we have font sizes available for each font
434 CPPUNIT_ASSERT( !rPair.second.empty());
436 free(pJSON);
439 void DesktopLOKTest::testCreateView()
441 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
442 CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument));
444 int nId0 = pDocument->m_pDocumentClass->getView(pDocument);
445 int nId1 = pDocument->m_pDocumentClass->createView(pDocument);
446 CPPUNIT_ASSERT_EQUAL(2, pDocument->m_pDocumentClass->getViewsCount(pDocument));
448 // Test getViewIds().
449 std::vector<int> aViewIds(2);
450 CPPUNIT_ASSERT(pDocument->m_pDocumentClass->getViewIds(pDocument, aViewIds.data(), aViewIds.size()));
451 CPPUNIT_ASSERT_EQUAL(nId0, aViewIds[0]);
452 CPPUNIT_ASSERT_EQUAL(nId1, aViewIds[1]);
454 // Make sure the created view is the active one, then switch to the old
455 // one.
456 CPPUNIT_ASSERT_EQUAL(nId1, pDocument->m_pDocumentClass->getView(pDocument));
457 pDocument->m_pDocumentClass->setView(pDocument, nId0);
458 CPPUNIT_ASSERT_EQUAL(nId0, pDocument->m_pDocumentClass->getView(pDocument));
460 pDocument->m_pDocumentClass->destroyView(pDocument, nId1);
461 CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument));
464 void DesktopLOKTest::testGetPartPageRectangles()
466 // Test that we get as many page rectangles as expected: blank document is
467 // one page.
468 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
469 char* pRectangles = pDocument->pClass->getPartPageRectangles(pDocument);
470 OUString sRectangles = OUString::fromUtf8(pRectangles);
472 std::vector<OUString> aRectangles;
473 sal_Int32 nIndex = 0;
476 OUString aRectangle = sRectangles.getToken(0, ';', nIndex);
477 if (!aRectangle.isEmpty())
478 aRectangles.push_back(aRectangle);
480 while (nIndex >= 0);
481 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aRectangles.size());
483 free(pRectangles);
486 void DesktopLOKTest::testGetFilterTypes()
488 LibLibreOffice_Impl aOffice;
489 char* pJSON = aOffice.m_pOfficeClass->getFilterTypes(&aOffice);
491 std::stringstream aStream(pJSON);
492 boost::property_tree::ptree aTree;
493 boost::property_tree::read_json(aStream, aTree);
495 CPPUNIT_ASSERT(!aTree.empty());
496 CPPUNIT_ASSERT_EQUAL(std::string("application/vnd.oasis.opendocument.text"), aTree.get_child("writer8").get_child("MediaType").get_value<std::string>());
497 free(pJSON);
500 void DesktopLOKTest::testSearchCalc()
502 LibLibreOffice_Impl aOffice;
503 LibLODocument_Impl* pDocument = loadDoc("search.ods");
504 pDocument->pClass->initializeForRendering(pDocument, nullptr);
505 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
507 uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
509 {"SearchItem.SearchString", uno::makeAny(OUString("foo"))},
510 {"SearchItem.Backward", uno::makeAny(false)},
511 {"SearchItem.Command", uno::makeAny(static_cast<sal_uInt16>(SvxSearchCmd::FIND_ALL))},
512 }));
513 comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues);
514 Scheduler::ProcessEventsToIdle();
516 std::vector<OString> aSelections;
517 sal_Int32 nIndex = 0;
520 OString aToken = m_aTextSelection.getToken(0, ';', nIndex);
521 aSelections.push_back(aToken);
522 } while (nIndex >= 0);
523 // This was 1, find-all only found one match.
524 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aSelections.size());
525 // Make sure that we get exactly as many rectangle lists as matches.
526 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), m_aSearchResultSelection.size());
527 // Result is on the first sheet.
528 CPPUNIT_ASSERT_EQUAL(0, m_aSearchResultPart[0]);
531 void DesktopLOKTest::testSearchAllNotificationsCalc()
533 LibLibreOffice_Impl aOffice;
534 LibLODocument_Impl* pDocument = loadDoc("search.ods");
535 pDocument->pClass->initializeForRendering(pDocument, nullptr);
536 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
538 uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
540 {"SearchItem.SearchString", uno::makeAny(OUString("foo"))},
541 {"SearchItem.Backward", uno::makeAny(false)},
542 {"SearchItem.Command", uno::makeAny(static_cast<sal_uInt16>(SvxSearchCmd::FIND_ALL))},
543 }));
544 comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues);
545 Scheduler::ProcessEventsToIdle();
547 // This was 1, make sure that we get no notifications about selection changes during search.
548 CPPUNIT_ASSERT_EQUAL(0, m_nSelectionBeforeSearchResult);
549 // But we do get the selection afterwards.
550 CPPUNIT_ASSERT(m_nSelectionAfterSearchResult > 0);
553 void DesktopLOKTest::testPaintTile()
555 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
556 int nCanvasWidth = 100;
557 int nCanvasHeight = 300;
558 sal_Int32 nStride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, nCanvasWidth);
559 std::vector<unsigned char> aBuffer(nStride * nCanvasHeight);
560 int nTilePosX = 0;
561 int nTilePosY = 0;
562 int nTileWidth = 1000;
563 int nTileHeight = 3000;
565 // This used to crash: paintTile() implementation did not handle
566 // nCanvasWidth != nCanvasHeight correctly, as usually both are just always
567 // 256.
568 pDocument->pClass->paintTile(pDocument, aBuffer.data(), nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight);
570 // This crashed in OutputDevice::DrawDeviceAlphaBitmap().
571 nCanvasWidth = 200;
572 nCanvasHeight = 200;
573 nTileWidth = 4000;
574 nTileHeight = 4000;
575 aBuffer.resize(nCanvasWidth * nCanvasHeight * 4);
576 pDocument->pClass->paintTile(pDocument, aBuffer.data(), nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight);
579 void DesktopLOKTest::testSaveAs()
581 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
582 utl::TempFile aTempFile;
583 aTempFile.EnableKillingFile();
584 CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, aTempFile.GetURL().toUtf8().getStr(), "png", nullptr));
587 void DesktopLOKTest::testSaveAsCalc()
589 LibLODocument_Impl* pDocument = loadDoc("search.ods");
590 utl::TempFile aTempFile;
591 aTempFile.EnableKillingFile();
592 CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, aTempFile.GetURL().toUtf8().getStr(), "png", nullptr));
595 void DesktopLOKTest::testPasteWriter()
597 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
598 OString aText("hello");
600 CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8", aText.getStr(), aText.getLength()));
602 pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false);
603 Scheduler::ProcessEventsToIdle();
604 char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr);
605 CPPUNIT_ASSERT_EQUAL(OString("hello"), OString(pText));
606 free(pText);
608 // textt/plain should be rejected.
609 CPPUNIT_ASSERT(!pDocument->pClass->paste(pDocument, "textt/plain;charset=utf-8", aText.getStr(), aText.getLength()));
610 // Writer is expected to support text/html.
611 CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/html", aText.getStr(), aText.getLength()));
613 // Overwrite doc contents with a HTML paste.
614 pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false);
615 Scheduler::ProcessEventsToIdle();
616 OString aComment("foo <!-- bar --> baz");
617 CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/html", aComment.getStr(), aComment.getLength()));
619 // Check if we have a comment.
620 uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
621 uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xTextDocument->getText(), uno::UNO_QUERY);
622 uno::Reference<container::XEnumeration> xParagraphEnumeration = xParagraphEnumerationAccess->createEnumeration();
623 uno::Reference<container::XEnumerationAccess> xParagraph(xParagraphEnumeration->nextElement(), uno::UNO_QUERY);
624 uno::Reference<container::XEnumeration> xTextPortionEnumeration = xParagraph->createEnumeration();
625 uno::Reference<beans::XPropertySet> xTextPortion(xTextPortionEnumeration->nextElement(), uno::UNO_QUERY);
626 CPPUNIT_ASSERT_EQUAL(OUString("Text"), xTextPortion->getPropertyValue("TextPortionType").get<OUString>());
627 // Without the accompanying fix in place, this test would have failed, as we had a comment
628 // between "foo" and "baz".
629 CPPUNIT_ASSERT(!xTextPortionEnumeration->hasMoreElements());
632 void DesktopLOKTest::testPasteWriterJPEG()
634 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
636 OUString aFileURL;
637 createFileURL("paste.jpg", aFileURL);
638 std::ifstream aImageStream(aFileURL.toUtf8().copy(strlen("file://")).getStr());
639 std::vector<char> aImageContents((std::istreambuf_iterator<char>(aImageStream)), std::istreambuf_iterator<char>());
641 CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "image/jpeg", aImageContents.data(), aImageContents.size()));
643 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
644 uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
645 // This was 0, JPEG was not handled as a format for clipboard paste.
646 CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xDrawPage->getCount());
648 uno::Reference<beans::XPropertySet> xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY);
649 // This was text::TextContentAnchorType_AT_PARAGRAPH.
650 CPPUNIT_ASSERT_EQUAL(text::TextContentAnchorType_AS_CHARACTER, xShape->getPropertyValue("AnchorType").get<text::TextContentAnchorType>());
652 // Delete the pasted picture, and paste again with a custom anchor type.
653 uno::Reference<lang::XComponent>(xShape, uno::UNO_QUERY)->dispose();
654 uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
656 {"AnchorType", uno::makeAny(static_cast<sal_uInt16>(text::TextContentAnchorType_AT_CHARACTER))},
657 }));
658 comphelper::dispatchCommand(".uno:Paste", aPropertyValues);
659 xShape.set(xDrawPage->getByIndex(0), uno::UNO_QUERY);
660 // This was text::TextContentAnchorType_AS_CHARACTER, AnchorType argument was ignored.
661 CPPUNIT_ASSERT_EQUAL(text::TextContentAnchorType_AT_CHARACTER, xShape->getPropertyValue("AnchorType").get<text::TextContentAnchorType>());
664 void DesktopLOKTest::testUndoWriter()
666 // Load a Writer document and press a key.
667 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
668 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0);
669 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't', 0);
670 Scheduler::ProcessEventsToIdle();
671 // Get undo info.
672 boost::property_tree::ptree aTree;
673 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:Undo");
674 std::stringstream aStream(pJSON);
675 free(pJSON);
676 CPPUNIT_ASSERT(!aStream.str().empty());
677 boost::property_tree::read_json(aStream, aTree);
678 // Make sure that pressing a key creates exactly one undo action.
679 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aTree.get_child("actions").size());
682 void DesktopLOKTest::testRowColumnHeaders()
685 * Payload example:
688 * "rows": [
690 * "size": "254.987250637468",
691 * "text": "1"
692 * },
694 * "size": "509.974501274936",
695 * "text": "2"
697 * ],
698 * "columns": [
700 * "size": "1274.93625318734",
701 * "text": "A"
702 * },
704 * "size": "2549.87250637468",
705 * "text": "B"
710 * "size" defines the bottom/right boundary of a row/column in twips (size between 0 and boundary)
711 * "text" has the header label in UTF-8
713 LibLODocument_Impl* pDocument = loadDoc("search.ods");
715 pDocument->pClass->initializeForRendering(pDocument, nullptr);
717 long nWidth = 0;
718 long nHeight = 0;
719 pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight);
720 long nX = rtl::math::round(nWidth / 4.0);
721 long nY = rtl::math::round(nHeight / 4.0);
722 nWidth = rtl::math::round(nWidth / 2.0);
723 nHeight = rtl::math::round(nHeight / 2.0);
725 std::stringstream aPayload;
726 aPayload << ".uno:ViewRowColumnHeaders?x=" << nX << "&y=" << nY << "&width=" << nWidth << "&height=" << nHeight;
728 boost::property_tree::ptree aTree;
729 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aPayload.str().c_str());
730 std::stringstream aStream(pJSON);
731 free(pJSON);
733 CPPUNIT_ASSERT(!aStream.str().empty());
735 boost::property_tree::read_json(aStream, aTree);
736 sal_Int32 nPrevious = 0;
737 bool bFirstHeader = true;
738 bool bNotEnoughHeaders = true;
739 for (boost::property_tree::ptree::value_type& rValue : aTree.get_child("rows"))
741 sal_Int32 nSize = OString(rValue.second.get<std::string>("size").c_str()).toInt32();
742 nSize *= 15; /* TWIPS_PER_PIXEL */
743 OString aText(rValue.second.get<std::string>("text").c_str());
745 if (bFirstHeader)
747 CPPUNIT_ASSERT(nSize <= nY);
748 CPPUNIT_ASSERT_EQUAL(OString("10"), aText);
749 bFirstHeader = false;
751 else
753 CPPUNIT_ASSERT(nSize > 0);
754 CPPUNIT_ASSERT(nPrevious < nSize);
755 if (nSize > nY + nHeight)
757 bNotEnoughHeaders = false;
758 break;
761 nPrevious = nSize;
763 CPPUNIT_ASSERT(!bNotEnoughHeaders);
765 nPrevious = 0;
766 bFirstHeader = true;
767 bNotEnoughHeaders = true;
768 for (boost::property_tree::ptree::value_type& rValue : aTree.get_child("columns"))
770 sal_Int32 nSize = OString(rValue.second.get<std::string>("size").c_str()).toInt32();
771 nSize *= 15; /* TWIPS_PER_PIXEL */
772 OString aText(rValue.second.get<std::string>("text").c_str());
773 if (bFirstHeader)
775 CPPUNIT_ASSERT(nSize <= nX);
776 CPPUNIT_ASSERT_EQUAL(OString("3"), aText);
777 bFirstHeader = false;
779 else
781 CPPUNIT_ASSERT(nSize > 0);
782 CPPUNIT_ASSERT(nPrevious < nSize);
783 if (nSize > nX + nWidth)
785 bNotEnoughHeaders = false;
786 break;
789 nPrevious = nSize;
791 CPPUNIT_ASSERT(!bNotEnoughHeaders);
794 void DesktopLOKTest::testHiddenRowHeaders()
796 LibLODocument_Impl* pDocument = loadDoc("hidden-row.ods");
798 pDocument->pClass->initializeForRendering(pDocument, nullptr);
800 long const nX = 0;
801 long const nY = 0;
802 long nWidth = 0;
803 long nHeight = 0;
804 pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight);
806 std::stringstream aPayload;
807 aPayload << ".uno:ViewRowColumnHeaders?x=" << nX << "&y=" << nY << "&width=" << nWidth << "&height=" << nHeight;
809 boost::property_tree::ptree aTree;
810 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aPayload.str().c_str());
811 std::stringstream aStream(pJSON);
812 free(pJSON);
813 CPPUNIT_ASSERT(!aStream.str().empty());
815 boost::property_tree::read_json(aStream, aTree);
816 sal_Int32 nPrevious = 0;
817 sal_Int32 nIndex = 0;
818 for (boost::property_tree::ptree::value_type& rValue : aTree.get_child("rows"))
820 sal_Int32 nSize = OString(rValue.second.get<std::string>("size").c_str()).toInt32();
822 if (nIndex++ == 2)
824 // nSize was 510, nPrevious was 255, i.e. hidden row wasn't reported as 0 height.
825 CPPUNIT_ASSERT_EQUAL(nPrevious, nSize);
826 break;
828 nPrevious = nSize;
832 void DesktopLOKTest::testCellCursor()
834 LibLODocument_Impl* pDocument = loadDoc("search.ods");
836 boost::property_tree::ptree aTree;
838 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:CellCursor?tileWidth=1&tileHeight=1&outputWidth=1&outputHeight=1");
840 std::stringstream aStream(pJSON);
841 free(pJSON);
842 CPPUNIT_ASSERT(!aStream.str().empty());
844 boost::property_tree::read_json(aStream, aTree);
846 OString aRectangle(aTree.get<std::string>("commandValues").c_str());
847 // cell cursor geometry + col + row
848 CPPUNIT_ASSERT_EQUAL(OString("0, 0, 1274, 254, 0, 0"), aRectangle);
851 void DesktopLOKTest::testCommandResult()
853 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
855 // the postUnoCommand() is supposed to be async, let's test it safely
856 // [no idea if it is async in reality - most probably we are operating
857 // under some solar mutex or something anyway ;-) - but...]
858 TimeValue aTimeValue = { 2 , 0 }; // 2 seconds max
860 // nothing is triggered when we have no callback yet, we just time out on
861 // the condition var.
862 m_aCommandResultCondition.reset();
863 pDocument->pClass->postUnoCommand(pDocument, ".uno:Bold", nullptr, true);
864 Scheduler::ProcessEventsToIdle();
865 m_aCommandResultCondition.wait(aTimeValue);
867 CPPUNIT_ASSERT(m_aCommandResult.isEmpty());
869 // but we get some real values when the callback is set up
870 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
872 m_aCommandResultCondition.reset();
873 pDocument->pClass->postUnoCommand(pDocument, ".uno:Bold", nullptr, true);
874 Scheduler::ProcessEventsToIdle();
875 m_aCommandResultCondition.wait(aTimeValue);
877 boost::property_tree::ptree aTree;
878 std::stringstream aStream(m_aCommandResult.getStr());
879 boost::property_tree::read_json(aStream, aTree);
881 CPPUNIT_ASSERT_EQUAL(std::string(".uno:Bold"), aTree.get_child("commandName").get_value<std::string>());
882 CPPUNIT_ASSERT_EQUAL(true, aTree.get_child("success").get_value<bool>());
885 void DesktopLOKTest::testWriterComments()
887 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
888 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
889 uno::Reference<awt::XReschedule> xToolkit(com::sun::star::awt::Toolkit::create(comphelper::getProcessComponentContext()), uno::UNO_QUERY);
891 // Insert a comment at the beginning of the document and wait till the main
892 // loop grabs the focus, so characters end up in the annotation window.
893 TimeValue const aTimeValue = {2 , 0}; // 2 seconds max
894 m_aCommandResultCondition.reset();
895 pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", nullptr, true);
896 Scheduler::ProcessEventsToIdle();
897 m_aCommandResultCondition.wait(aTimeValue);
898 CPPUNIT_ASSERT(!m_aCommandResult.isEmpty());
899 xToolkit->reschedule();
901 // Test that we have a comment.
902 uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
903 uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xTextDocument->getText(), uno::UNO_QUERY);
904 uno::Reference<container::XEnumeration> xParagraphEnumeration = xParagraphEnumerationAccess->createEnumeration();
905 uno::Reference<container::XEnumerationAccess> xParagraph(xParagraphEnumeration->nextElement(), uno::UNO_QUERY);
906 uno::Reference<container::XEnumeration> xTextPortionEnumeration = xParagraph->createEnumeration();
907 uno::Reference<beans::XPropertySet> xTextPortion(xTextPortionEnumeration->nextElement(), uno::UNO_QUERY);
908 CPPUNIT_ASSERT_EQUAL(OUString("Annotation"), xTextPortion->getPropertyValue("TextPortionType").get<OUString>());
910 // Type "test" and finish editing.
911 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0);
912 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'e', 0);
913 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 's', 0);
914 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0);
915 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, com::sun::star::awt::Key::ESCAPE);
916 Scheduler::ProcessEventsToIdle();
918 // Test that the typed characters ended up in the right window.
919 auto xTextField = xTextPortion->getPropertyValue("TextField").get< uno::Reference<beans::XPropertySet> >();
920 // This was empty, typed characters ended up in the body text.
921 CPPUNIT_ASSERT_EQUAL(OUString("test"), xTextField->getPropertyValue("Content").get<OUString>());
924 void DesktopLOKTest::testTrackChanges()
926 // Load a document and create two views.
927 LibLibreOffice_Impl aOffice;
928 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
929 pDocument->pClass->initializeForRendering(pDocument, nullptr);
930 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
931 pDocument->pClass->createView(pDocument);
932 pDocument->pClass->initializeForRendering(pDocument, nullptr);
933 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
934 Scheduler::ProcessEventsToIdle();
936 // Enable trak changes and assert that both views get notified.
937 m_nTrackChanges = 0;
938 pDocument->pClass->postUnoCommand(pDocument, ".uno:TrackChanges", nullptr, false);
939 Scheduler::ProcessEventsToIdle();
940 // This was 1, only the active view was notified.
941 CPPUNIT_ASSERT_EQUAL(2, m_nTrackChanges);
944 void DesktopLOKTest::testSheetOperations()
946 LibLODocument_Impl* pDocument = loadDoc("sheets.ods");
948 // insert the last sheet
949 pDocument->pClass->postUnoCommand(pDocument, ".uno:Insert",
950 "{ \"Name\": { \"type\": \"string\", \"value\": \"LastSheet\" }, \"Index\": { \"type\": \"long\", \"value\": 0 } }", false);
952 // insert the first sheet
953 pDocument->pClass->postUnoCommand(pDocument, ".uno:Insert",
954 "{ \"Name\": { \"type\": \"string\", \"value\": \"FirstSheet\" }, \"Index\": { \"type\": \"long\", \"value\": 1 } }", false);
956 // rename the \"Sheet1\" (2nd now) to \"Renamed\"
957 pDocument->pClass->postUnoCommand(pDocument, ".uno:Name",
958 "{ \"Name\": { \"type\": \"string\", \"value\": \"Renamed\" }, \"Index\": { \"type\": \"long\", \"value\": 2 } }", false);
960 // delete the \"Sheet2\" (3rd)
961 pDocument->pClass->postUnoCommand(pDocument, ".uno:Remove",
962 "{ \"Index\": { \"type\": \"long\", \"value\": 3 } }", false);
964 Scheduler::ProcessEventsToIdle();
965 CPPUNIT_ASSERT_EQUAL(6, pDocument->pClass->getParts(pDocument));
967 std::vector<OString> aExpected = { "FirstSheet", "Renamed", "Sheet3", "Sheet4", "Sheet5", "LastSheet" };
968 for (int i = 0; i < 6; ++i)
970 CPPUNIT_ASSERT_EQUAL(aExpected[i], OString(pDocument->pClass->getPartName(pDocument, i)));
974 void DesktopLOKTest::testSheetSelections()
976 LibLODocument_Impl* pDocument = loadDoc("sheets.ods", LOK_DOCTYPE_SPREADSHEET);
977 pDocument->pClass->initializeForRendering(pDocument, nullptr);
978 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
981 * Check if selection data is correct
983 // Values in twips
984 int row5 = 1150;
985 int col1 = 1100;
986 int const col2 = 2200;
987 int const col3 = 3300;
988 int col4 = 4400;
989 int col5 = 5500;
991 // Select row 5 from column 1 through column 5
992 pDocument->pClass->postMouseEvent(pDocument,
993 LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
994 col1, row5,
995 1, 1, 0);
996 pDocument->pClass->postMouseEvent(pDocument,
997 LOK_MOUSEEVENT_MOUSEMOVE,
998 col2, row5,
999 1, 1, 0);
1000 pDocument->pClass->postMouseEvent(pDocument,
1001 LOK_MOUSEEVENT_MOUSEMOVE,
1002 col3, row5,
1003 1, 1, 0);
1004 pDocument->pClass->postMouseEvent(pDocument,
1005 LOK_MOUSEEVENT_MOUSEMOVE,
1006 col4, row5,
1007 1, 1, 0);
1008 pDocument->pClass->postMouseEvent(pDocument,
1009 LOK_MOUSEEVENT_MOUSEMOVE,
1010 col5, row5,
1011 1, 1, 0);
1012 pDocument->pClass->postMouseEvent(pDocument,
1013 LOK_MOUSEEVENT_MOUSEBUTTONUP,
1014 col5, row5,
1015 1, 1, 0);
1016 Scheduler::ProcessEventsToIdle();
1018 // Copy the contents and check if matches expected data
1020 char* pUsedMimeType = nullptr;
1021 char* pCopiedContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pUsedMimeType);
1022 std::vector<long> aExpected = {5, 6, 7, 8, 9};
1023 std::istringstream iss(pCopiedContent);
1024 for (size_t i = 0; i < aExpected.size(); i++)
1026 std::string token;
1027 iss >> token;
1028 CPPUNIT_ASSERT_EQUAL(aExpected[i], strtol(token.c_str(), nullptr, 10));
1031 free(pUsedMimeType);
1032 free(pCopiedContent);
1036 * Check if clicking inside the selection deselects the whole selection
1038 int const row10 = 2400;
1039 // Select starting from row5, col1 to row10, col5
1040 pDocument->pClass->postMouseEvent(pDocument,
1041 LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
1042 col1, row5,
1043 1, 1, 0);
1044 pDocument->pClass->postMouseEvent(pDocument,
1045 LOK_MOUSEEVENT_MOUSEMOVE,
1046 col5, row5,
1047 1, 1, 0);
1048 pDocument->pClass->postMouseEvent(pDocument,
1049 LOK_MOUSEEVENT_MOUSEBUTTONUP,
1050 col5, row10,
1051 1, 1, 0);
1053 // Click at row5, col4
1054 pDocument->pClass->postMouseEvent(pDocument,
1055 LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
1056 col4, row5,
1057 1, 1, 0);
1058 pDocument->pClass->postMouseEvent(pDocument,
1059 LOK_MOUSEEVENT_MOUSEBUTTONUP,
1060 col4, row5,
1061 1, 1, 0);
1062 Scheduler::ProcessEventsToIdle();
1064 // Selected text should get deselected and copying should give us
1065 // content of only one cell, now
1067 char* pUsedMimeType = nullptr;
1068 char* pCopiedContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pUsedMimeType);
1069 std::vector<long> aExpected = { 8 };
1070 std::istringstream iss(pCopiedContent);
1071 for (size_t i = 0; i < aExpected.size(); i++)
1073 std::string token;
1074 iss >> token;
1075 CPPUNIT_ASSERT_EQUAL(aExpected[i], strtol(token.c_str(), nullptr, 10));
1078 free(pUsedMimeType);
1079 free(pCopiedContent);
1083 namespace {
1085 void verifyContextMenuStructure(boost::property_tree::ptree& aRoot)
1087 for (const auto& aItemPair: aRoot)
1089 // This is an array, so no key
1090 CPPUNIT_ASSERT_EQUAL(std::string(aItemPair.first), std::string(""));
1092 boost::property_tree::ptree aItemValue = aItemPair.second;
1093 boost::optional<boost::property_tree::ptree&> aText = aItemValue.get_child_optional("text");
1094 boost::optional<boost::property_tree::ptree&> aType = aItemValue.get_child_optional("type");
1095 boost::optional<boost::property_tree::ptree&> aCommand = aItemValue.get_child_optional("command");
1096 boost::optional<boost::property_tree::ptree&> aSubmenu = aItemValue.get_child_optional("menu");
1097 boost::optional<boost::property_tree::ptree&> aEnabled = aItemValue.get_child_optional("enabled");
1098 boost::optional<boost::property_tree::ptree&> aChecktype = aItemValue.get_child_optional("checktype");
1099 boost::optional<boost::property_tree::ptree&> aChecked = aItemValue.get_child_optional("checked");
1101 // type is omnipresent
1102 CPPUNIT_ASSERT( aType );
1104 // separator doesn't have any other attribs
1105 if ( aType.get().data() == "separator" )
1107 CPPUNIT_ASSERT( !aText && !aCommand && !aSubmenu && !aEnabled && !aChecktype && !aChecked );
1109 else if ( aType.get().data() == "command" )
1111 CPPUNIT_ASSERT( aCommand && aText );
1113 else if ( aType.get().data() == "menu")
1115 CPPUNIT_ASSERT( aSubmenu && aText );
1116 verifyContextMenuStructure( aSubmenu.get() );
1119 if ( aChecktype )
1121 CPPUNIT_ASSERT( aChecktype.get().data() == "radio" ||
1122 aChecktype.get().data() == "checkmark" ||
1123 aChecktype.get().data() == "auto" );
1125 CPPUNIT_ASSERT( aChecked &&
1126 ( aChecked.get().data() == "true" || aChecked.get().data() == "false" ) );
1132 boost::optional<boost::property_tree::ptree>
1133 getContextMenuItem(boost::property_tree::ptree& aMenu, std::string const & unoSelector)
1135 boost::optional<boost::property_tree::ptree> aMenuItem;
1136 for (const auto& aItemPair: aMenu)
1138 boost::property_tree::ptree aItemValue = aItemPair.second;
1140 boost::optional<boost::property_tree::ptree&> aCommand = aItemValue.get_child_optional("command");
1141 if (aCommand && aCommand.get().data() == unoSelector )
1143 aMenuItem = aItemValue;
1144 break;
1148 return aMenuItem;
1151 } // end anonymous namespace
1153 void DesktopLOKTest::testContextMenuCalc()
1155 LibLODocument_Impl* pDocument = loadDoc("sheet_with_image.ods", LOK_DOCTYPE_SPREADSHEET);
1156 pDocument->pClass->initializeForRendering(pDocument, nullptr);
1157 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
1159 // Values in twips
1160 Point aPointOnImage(1150, 1100);
1161 pDocument->pClass->postMouseEvent(pDocument,
1162 LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
1163 aPointOnImage.X(), aPointOnImage.Y(),
1164 1, 4, 0);
1165 Scheduler::ProcessEventsToIdle();
1167 TimeValue const aTimeValue = {2 , 0}; // 2 seconds max
1168 m_aContextMenuCondition.wait(aTimeValue);
1170 CPPUNIT_ASSERT( !m_aContextMenuResult.empty() );
1171 boost::optional<boost::property_tree::ptree&> aMenu = m_aContextMenuResult.get_child_optional("menu");
1172 CPPUNIT_ASSERT( aMenu );
1173 verifyContextMenuStructure( aMenu.get() );
1175 // tests for calc specific context menu
1176 // Cut is enabled
1178 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut");
1179 CPPUNIT_ASSERT(aMenuItem);
1181 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1182 CPPUNIT_ASSERT(aEnabled);
1183 CPPUNIT_ASSERT_EQUAL(std::string(aEnabled.get().data()), std::string("true"));
1186 // Copy is enabled
1188 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy");
1189 CPPUNIT_ASSERT(aMenuItem);
1191 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1192 CPPUNIT_ASSERT(aEnabled);
1193 CPPUNIT_ASSERT_EQUAL(std::string(aEnabled.get().data()), std::string("true"));
1196 // Paste is enabled
1198 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste");
1199 CPPUNIT_ASSERT(aMenuItem);
1201 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1202 CPPUNIT_ASSERT(aEnabled);
1203 CPPUNIT_ASSERT_EQUAL(std::string(aEnabled.get().data()), std::string("true"));
1206 // Remove hyperlink is disabled
1208 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:DeleteShapeHyperlink");
1209 CPPUNIT_ASSERT(aMenuItem);
1211 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1212 CPPUNIT_ASSERT(aEnabled);
1213 CPPUNIT_ASSERT_EQUAL(std::string(aEnabled.get().data()), std::string("false"));
1216 // open hyperlink is disabled
1218 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:OpenHyperlinkOnCursor");
1219 CPPUNIT_ASSERT(aMenuItem);
1221 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1222 CPPUNIT_ASSERT(aEnabled);
1223 CPPUNIT_ASSERT_EQUAL(std::string(aEnabled.get().data()), std::string("false"));
1226 // checkbutton tests
1228 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:AnchorMenu");
1229 CPPUNIT_ASSERT(aMenuItem);
1231 boost::optional<boost::property_tree::ptree&> aSubmenu = aMenuItem.get().get_child_optional("menu");
1232 CPPUNIT_ASSERT(aSubmenu);
1234 boost::optional<boost::property_tree::ptree> aMenuItemToPage = getContextMenuItem(aSubmenu.get(), ".uno:SetAnchorToPage");
1235 CPPUNIT_ASSERT(aMenuItemToPage);
1237 boost::optional<boost::property_tree::ptree> aMenuItemToCell = getContextMenuItem(aSubmenu.get(), ".uno:SetAnchorToCell");
1238 CPPUNIT_ASSERT(aMenuItemToCell);
1240 // these are radio buttons
1241 boost::optional<boost::property_tree::ptree&> aChecktypeToPage = aMenuItemToPage.get().get_child_optional("checktype");
1242 CPPUNIT_ASSERT(aChecktypeToPage);
1243 CPPUNIT_ASSERT_EQUAL(std::string(aChecktypeToPage.get().data()), std::string("radio"));
1245 boost::optional<boost::property_tree::ptree&> aChecktypeToCell = aMenuItemToCell.get().get_child_optional("checktype");
1246 CPPUNIT_ASSERT(aChecktypeToCell);
1247 CPPUNIT_ASSERT_EQUAL(std::string(aChecktypeToCell.get().data()), std::string("radio"));
1249 // ToPage is checked
1250 boost::optional<boost::property_tree::ptree&> aCheckedToPage = aMenuItemToPage.get().get_child_optional("checked");
1251 CPPUNIT_ASSERT(aCheckedToPage);
1252 CPPUNIT_ASSERT_EQUAL(std::string(aCheckedToPage.get().data()), std::string("true"));
1254 // ToCell is unchecked
1255 boost::optional<boost::property_tree::ptree&> aCheckedToCell = aMenuItemToCell.get().get_child_optional("checked");
1256 CPPUNIT_ASSERT(aCheckedToCell);
1257 CPPUNIT_ASSERT_EQUAL(std::string(aCheckedToCell.get().data()), std::string("false"));
1261 void DesktopLOKTest::testContextMenuWriter()
1263 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1264 pDocument->pClass->initializeForRendering(pDocument, nullptr);
1265 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
1267 Point aRandomPoint(1150, 1100);
1268 pDocument->pClass->postMouseEvent(pDocument,
1269 LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
1270 aRandomPoint.X(), aRandomPoint.Y(),
1271 1, 4, 0);
1272 Scheduler::ProcessEventsToIdle();
1274 TimeValue const aTimeValue = {2 , 0}; // 2 seconds max
1275 m_aContextMenuCondition.wait(aTimeValue);
1277 CPPUNIT_ASSERT( !m_aContextMenuResult.empty() );
1278 boost::optional<boost::property_tree::ptree&> aMenu = m_aContextMenuResult.get_child_optional("menu");
1279 CPPUNIT_ASSERT( aMenu );
1280 verifyContextMenuStructure( aMenu.get() );
1282 // tests for writer specific context menu
1283 // Cut is disabled
1285 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut");
1286 CPPUNIT_ASSERT(aMenuItem);
1288 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1289 CPPUNIT_ASSERT(aEnabled);
1290 CPPUNIT_ASSERT_EQUAL(std::string(aEnabled.get().data()), std::string("false"));
1293 // Copy is disabled
1295 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy");
1296 CPPUNIT_ASSERT(aMenuItem);
1298 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1299 CPPUNIT_ASSERT(aEnabled);
1300 CPPUNIT_ASSERT_EQUAL(std::string(aEnabled.get().data()), std::string("false"));
1303 // Paste is enabled
1305 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste");
1306 CPPUNIT_ASSERT(aMenuItem);
1308 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1309 CPPUNIT_ASSERT(aEnabled);
1310 CPPUNIT_ASSERT_EQUAL(std::string(aEnabled.get().data()), std::string("true"));
1314 void DesktopLOKTest::testContextMenuImpress()
1316 LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp", LOK_DOCTYPE_PRESENTATION);
1317 pDocument->pClass->initializeForRendering(pDocument, nullptr);
1318 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
1320 // random point where we don't hit an underlying comment or text box
1321 Point aRandomPoint(10, 1150);
1322 pDocument->pClass->postMouseEvent(pDocument,
1323 LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
1324 aRandomPoint.X(), aRandomPoint.Y(),
1325 1, 4, 0);
1326 Scheduler::ProcessEventsToIdle();
1328 TimeValue const aTimeValue = {2 , 0}; // 2 seconds max
1329 m_aContextMenuCondition.wait(aTimeValue);
1331 CPPUNIT_ASSERT( !m_aContextMenuResult.empty() );
1332 boost::optional<boost::property_tree::ptree&> aMenu = m_aContextMenuResult.get_child_optional("menu");
1333 CPPUNIT_ASSERT( aMenu );
1334 verifyContextMenuStructure( aMenu.get() );
1336 // tests for impress specific context menu
1337 // Cut is disabled
1339 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut");
1340 CPPUNIT_ASSERT(aMenuItem);
1342 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1343 CPPUNIT_ASSERT(aEnabled);
1344 CPPUNIT_ASSERT_EQUAL(std::string(aEnabled.get().data()), std::string("false"));
1347 // Copy is disabled
1349 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy");
1350 CPPUNIT_ASSERT(aMenuItem);
1352 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1353 CPPUNIT_ASSERT(aEnabled);
1354 CPPUNIT_ASSERT_EQUAL(std::string(aEnabled.get().data()), std::string("false"));
1357 // Paste is enabled
1359 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste");
1360 CPPUNIT_ASSERT(aMenuItem);
1362 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1363 CPPUNIT_ASSERT(aEnabled);
1364 CPPUNIT_ASSERT_EQUAL(std::string(aEnabled.get().data()), std::string("true"));
1367 // SaveBackground is disabled
1369 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:SaveBackground");
1370 CPPUNIT_ASSERT(aMenuItem);
1372 boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled");
1373 CPPUNIT_ASSERT(aEnabled);
1374 CPPUNIT_ASSERT_EQUAL(std::string(aEnabled.get().data()), std::string("false"));
1377 // checkbutton tests
1379 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:ShowRuler");
1380 CPPUNIT_ASSERT(aMenuItem);
1382 boost::optional<boost::property_tree::ptree&> aChecktype = aMenuItem.get().get_child_optional("checktype");
1383 CPPUNIT_ASSERT(aChecktype);
1384 CPPUNIT_ASSERT_EQUAL(std::string(aChecktype.get().data()), std::string("checkmark"));
1386 boost::optional<boost::property_tree::ptree&> aChecked = aMenuItem.get().get_child_optional("checked");
1387 CPPUNIT_ASSERT(aChecked);
1388 CPPUNIT_ASSERT_EQUAL(std::string(aChecked.get().data()), std::string("false"));
1391 // Checkbutton tests inside SnapLines submenu
1393 boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:SnapLinesMenu");
1394 CPPUNIT_ASSERT(aMenuItem);
1396 boost::optional<boost::property_tree::ptree&> aSubmenu = aMenuItem.get().get_child_optional("menu");
1397 CPPUNIT_ASSERT(aSubmenu);
1399 boost::optional<boost::property_tree::ptree> aMenuItemHelpVis = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesVisible");
1400 CPPUNIT_ASSERT(aMenuItemHelpVis);
1402 boost::optional<boost::property_tree::ptree> aMenuItemHelpUse = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesUse");
1403 CPPUNIT_ASSERT(aMenuItemHelpUse);
1405 boost::optional<boost::property_tree::ptree> aMenuItemHelpFront = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesFront");
1406 CPPUNIT_ASSERT(aMenuItemHelpFront);
1408 // these are checkmarks
1409 boost::optional<boost::property_tree::ptree&> aChecktypeHelpVis = aMenuItemHelpVis.get().get_child_optional("checktype");
1410 CPPUNIT_ASSERT(aChecktypeHelpVis);
1411 CPPUNIT_ASSERT_EQUAL(std::string(aChecktypeHelpVis.get().data()), std::string("checkmark"));
1413 boost::optional<boost::property_tree::ptree&> aChecktypeHelpUse = aMenuItemHelpUse.get().get_child_optional("checktype");
1414 CPPUNIT_ASSERT(aChecktypeHelpUse);
1415 CPPUNIT_ASSERT_EQUAL(std::string(aChecktypeHelpUse.get().data()), std::string("checkmark"));
1417 boost::optional<boost::property_tree::ptree&> aChecktypeHelpFront = aMenuItemHelpFront.get().get_child_optional("checktype");
1418 CPPUNIT_ASSERT(aChecktypeHelpFront);
1419 CPPUNIT_ASSERT_EQUAL(std::string(aChecktypeHelpFront.get().data()), std::string("checkmark"));
1421 // HelplineVisible is unchecked
1422 boost::optional<boost::property_tree::ptree&> aCheckedHelpVis = aMenuItemHelpVis.get().get_child_optional("checked");
1423 CPPUNIT_ASSERT(aCheckedHelpVis);
1424 CPPUNIT_ASSERT_EQUAL(std::string(aCheckedHelpVis.get().data()), std::string("false"));
1426 // HelplineUse is checked
1427 boost::optional<boost::property_tree::ptree&> aCheckedHelpUse = aMenuItemHelpUse.get().get_child_optional("checked");
1428 CPPUNIT_ASSERT(aCheckedHelpUse);
1429 CPPUNIT_ASSERT_EQUAL(std::string(aCheckedHelpUse.get().data()), std::string("true"));
1431 // HelplineFront is checked
1432 boost::optional<boost::property_tree::ptree&> aCheckedHelpFront = aMenuItemHelpFront.get().get_child_optional("checked");
1433 CPPUNIT_ASSERT(aCheckedHelpFront);
1434 CPPUNIT_ASSERT_EQUAL(std::string(aCheckedHelpFront.get().data()), std::string("true"));
1438 static void callbackCompressionTest(const int type, const char* payload, void* data)
1440 std::vector<std::tuple<int, std::string>>* notifs = static_cast<std::vector<std::tuple<int, std::string>>*>(data);
1441 notifs->emplace_back(type, std::string(payload ? payload : "(nil)"));
1444 void DesktopLOKTest::testNotificationCompression()
1446 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1447 std::vector<std::tuple<int, std::string>> notifs;
1448 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1450 handler->queue(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, ""); // 0
1451 handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10"); // Superseded.
1452 handler->queue(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, ""); // Should be dropped.
1453 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10"); // 1
1454 handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10"); // Should be dropped.
1455 handler->queue(LOK_CALLBACK_TEXT_SELECTION, ""); // Superseded.
1456 handler->queue(LOK_CALLBACK_STATE_CHANGED, ""); // 2
1457 handler->queue(LOK_CALLBACK_STATE_CHANGED, ".uno:Bold"); // 3
1458 handler->queue(LOK_CALLBACK_STATE_CHANGED, ""); // 4
1459 handler->queue(LOK_CALLBACK_MOUSE_POINTER, "text"); // 5
1460 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10"); // Should be dropped.
1461 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10"); // Should be dropped.
1462 handler->queue(LOK_CALLBACK_MOUSE_POINTER, "text"); // Should be dropped.
1463 handler->queue(LOK_CALLBACK_TEXT_SELECTION_START, "15, 25, 15, 10"); // Superseded.
1464 handler->queue(LOK_CALLBACK_TEXT_SELECTION_END, "15, 25, 15, 10"); // Superseded.
1465 handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10"); // Superseded.
1466 handler->queue(LOK_CALLBACK_TEXT_SELECTION_START, "15, 25, 15, 10"); // Should be dropped.
1467 handler->queue(LOK_CALLBACK_TEXT_SELECTION_END, "15, 25, 15, 10"); // Should be dropped.
1468 handler->queue(LOK_CALLBACK_TEXT_SELECTION, ""); // 7
1469 handler->queue(LOK_CALLBACK_TEXT_SELECTION_START, "15, 25, 15, 10"); // 8
1470 handler->queue(LOK_CALLBACK_TEXT_SELECTION_END, "15, 25, 15, 10"); // 9
1471 handler->queue(LOK_CALLBACK_CELL_CURSOR, "15, 25, 15, 10"); // 10
1472 handler->queue(LOK_CALLBACK_CURSOR_VISIBLE, ""); // 11
1473 handler->queue(LOK_CALLBACK_CELL_CURSOR, "15, 25, 15, 10"); // Should be dropped.
1474 handler->queue(LOK_CALLBACK_CELL_FORMULA, "blah"); // 12
1475 handler->queue(LOK_CALLBACK_SET_PART, "1"); // 13
1476 handler->queue(LOK_CALLBACK_STATE_CHANGED, ".uno:AssignLayout=20"); // Superseded
1477 handler->queue(LOK_CALLBACK_CURSOR_VISIBLE, ""); // Should be dropped.
1478 handler->queue(LOK_CALLBACK_CELL_FORMULA, "blah"); // Should be dropped.
1479 handler->queue(LOK_CALLBACK_SET_PART, "1"); // Should be dropped.
1480 handler->queue(LOK_CALLBACK_STATE_CHANGED, ".uno:AssignLayout=1"); // 14
1482 Scheduler::ProcessEventsToIdle();
1484 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(14), notifs.size());
1486 size_t i = 0;
1487 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR), std::get<0>(notifs[i]));
1488 CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++]));
1490 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1491 CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++]));
1493 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i]));
1494 CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++]));
1496 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i]));
1497 CPPUNIT_ASSERT_EQUAL(std::string(".uno:Bold"), std::get<1>(notifs[i++]));
1499 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i]));
1500 CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++]));
1502 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_MOUSE_POINTER), std::get<0>(notifs[i]));
1503 CPPUNIT_ASSERT_EQUAL(std::string("text"), std::get<1>(notifs[i++]));
1505 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_TEXT_SELECTION), std::get<0>(notifs[i]));
1506 CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++]));
1508 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_TEXT_SELECTION_START), std::get<0>(notifs[i]));
1509 CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++]));
1511 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_TEXT_SELECTION_END), std::get<0>(notifs[i]));
1512 CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++]));
1514 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_CELL_CURSOR), std::get<0>(notifs[i]));
1515 CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++]));
1517 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_CURSOR_VISIBLE), std::get<0>(notifs[i]));
1518 CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++]));
1520 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_CELL_FORMULA), std::get<0>(notifs[i]));
1521 CPPUNIT_ASSERT_EQUAL(std::string("blah"), std::get<1>(notifs[i++]));
1523 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_SET_PART), std::get<0>(notifs[i]));
1524 CPPUNIT_ASSERT_EQUAL(std::string("1"), std::get<1>(notifs[i++]));
1526 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i]));
1527 CPPUNIT_ASSERT_EQUAL(std::string(".uno:AssignLayout=1"), std::get<1>(notifs[i++]));
1530 void DesktopLOKTest::testTileInvalidationCompression()
1532 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1534 comphelper::LibreOfficeKit::setPartInInvalidation(true);
1535 comphelper::ScopeGuard aGuard([]()
1537 comphelper::LibreOfficeKit::setPartInInvalidation(false);
1540 // Single part merging
1542 std::vector<std::tuple<int, std::string>> notifs;
1543 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1545 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0");
1546 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0");
1547 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-100, -50, 500, 650, 0");
1548 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, 0");
1549 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "100, 100, 200, 200, 0");
1551 Scheduler::ProcessEventsToIdle();
1553 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size());
1555 size_t i = 0;
1556 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1557 CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 400, 600, 0"), std::get<1>(notifs[i++]));
1560 // Part Number
1562 std::vector<std::tuple<int, std::string>> notifs;
1563 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1565 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0");
1566 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 1"); // Different part
1567 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 0, 0, 2"); // Invalid
1568 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-121, -121, 200, 200, 0"); // Inside first
1569 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, 1"); // Invalid
1571 Scheduler::ProcessEventsToIdle();
1573 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size());
1575 size_t i = 0;
1576 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1577 CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 200, 200, 1"), std::get<1>(notifs[i++]));
1579 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1580 CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 239, 239, 0"), std::get<1>(notifs[i++]));
1583 // All Parts
1585 std::vector<std::tuple<int, std::string>> notifs;
1586 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1588 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0"); // 0
1589 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 1"); // 1: Different part
1590 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 0, 0, -1"); // Invalid
1591 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-121, -121, 200, 200, -1"); // 0: All parts
1592 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, -1"); // Invalid
1593 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-100, -100, 1200, 1200, -1"); // 0: All parts
1594 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 3"); // Overlapped
1595 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "1000, 1000, 1239, 1239, 2"); // 1: Unique region
1597 Scheduler::ProcessEventsToIdle();
1599 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size());
1601 size_t i = 0;
1602 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1603 CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 1100, 1100, -1"), std::get<1>(notifs[i++]));
1605 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1606 CPPUNIT_ASSERT_EQUAL(std::string("1000, 1000, 1239, 1239, 2"), std::get<1>(notifs[i++]));
1609 // All Parts (partial)
1611 std::vector<std::tuple<int, std::string>> notifs;
1612 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1614 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 0"); // 0
1615 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 100, 100, 1"); // 1: Different part
1616 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 0, 0, -1"); // Invalid
1617 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "150, 150, 50, 50, -1"); // 2: All-parts
1618 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, -1"); // Invalid
1619 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "150, 150, 40, 40, 3"); // Overlapped w/ 2
1620 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 4"); // 3: Unique
1621 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "1000, 1000, 1239, 1239, 1"); // 4: Unique
1623 Scheduler::ProcessEventsToIdle();
1625 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(5), notifs.size());
1627 size_t i = 0;
1628 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1629 CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 200, 200, 0"), std::get<1>(notifs[i++]));
1631 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1632 CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 100, 100, 1"), std::get<1>(notifs[i++]));
1634 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1635 CPPUNIT_ASSERT_EQUAL(std::string("150, 150, 50, 50, -1"), std::get<1>(notifs[i++]));
1637 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1638 CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 200, 200, 4"), std::get<1>(notifs[i++]));
1640 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1641 CPPUNIT_ASSERT_EQUAL(std::string("1000, 1000, 1239, 1239, 1"), std::get<1>(notifs[i++]));
1644 // Merge with "EMPTY"
1646 std::vector<std::tuple<int, std::string>> notifs;
1647 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1649 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0");
1650 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "EMPTY, 0");
1651 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 240, 0");
1652 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-121, -121, 300, 300, 0");
1653 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, 0");
1655 Scheduler::ProcessEventsToIdle();
1657 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size());
1659 size_t i = 0;
1660 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i]));
1661 CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 1000000000, 1000000000, 0"), std::get<1>(notifs[i++]));
1665 void DesktopLOKTest::testPartInInvalidation()
1667 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1668 // No part in invalidation: merge.
1670 std::vector<std::tuple<int, std::string>> notifs;
1671 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1673 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10");
1674 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "20, 10, 20, 10");
1676 Scheduler::ProcessEventsToIdle();
1678 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size());
1680 CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[0]));
1681 CPPUNIT_ASSERT_EQUAL(std::string("10, 10, 30, 10"), std::get<1>(notifs[0]));
1683 // No part in invalidation: don't merge.
1685 std::vector<std::tuple<int, std::string>> notifs;
1686 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1688 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10");
1689 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "40, 10, 20, 10");
1691 Scheduler::ProcessEventsToIdle();
1693 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size());
1696 // Part in invalidation, intersection and parts match -> merge.
1698 comphelper::LibreOfficeKit::setPartInInvalidation(true);
1699 comphelper::ScopeGuard aGuard([]()
1701 comphelper::LibreOfficeKit::setPartInInvalidation(false);
1704 std::vector<std::tuple<int, std::string>> notifs;
1705 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1707 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10, 0");
1708 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "20, 10, 20, 10, 0");
1710 Scheduler::ProcessEventsToIdle();
1712 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size());
1714 // Part in invalidation, intersection and parts don't match -> don't merge.
1716 comphelper::LibreOfficeKit::setPartInInvalidation(true);
1717 comphelper::ScopeGuard aGuard([]()
1719 comphelper::LibreOfficeKit::setPartInInvalidation(false);
1722 std::vector<std::tuple<int, std::string>> notifs;
1723 std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, &notifs));
1725 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10, 0");
1726 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "20, 10, 20, 10, 1");
1728 Scheduler::ProcessEventsToIdle();
1730 // This failed as RectangleAndPart::Create() always assumed no part in
1731 // payload, so this was merged -> it was 1.
1732 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size());
1736 void DesktopLOKTest::testDialogInput()
1738 comphelper::LibreOfficeKit::setActive();
1739 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1740 pDocument->pClass->postUnoCommand(pDocument, ".uno:HyperlinkDialog", nullptr, false);
1741 Scheduler::ProcessEventsToIdle();
1743 SfxViewShell* pViewShell = SfxViewShell::Current();
1744 pViewShell->GetViewFrame()->GetBindings().Update();
1746 VclPtr<vcl::Window> pWindow(Application::GetActiveTopWindow());
1747 CPPUNIT_ASSERT(pWindow);
1749 Control* pCtrlFocused = GetFocusControl(pWindow.get());
1750 CPPUNIT_ASSERT(pCtrlFocused);
1751 ComboBox* pCtrlURL = dynamic_cast<ComboBox*>(pCtrlFocused);
1752 CPPUNIT_ASSERT(pCtrlURL);
1753 CPPUNIT_ASSERT_EQUAL(OUString(""), pCtrlURL->GetText());
1755 vcl::LOKWindowId nDialogId = pWindow->GetLOKWindowId();
1756 pDocument->pClass->postWindowExtTextInputEvent(pDocument, nDialogId, LOK_EXT_TEXTINPUT, "wiki.");
1757 pDocument->pClass->postWindowExtTextInputEvent(pDocument, nDialogId, LOK_EXT_TEXTINPUT_END, "wiki.");
1758 pDocument->pClass->removeTextContext(pDocument, nDialogId, 1, 0);
1759 Scheduler::ProcessEventsToIdle();
1760 CPPUNIT_ASSERT_EQUAL(OUString("wiki"), pCtrlURL->GetText());
1762 static_cast<SystemWindow*>(pWindow.get())->Close();
1763 Scheduler::ProcessEventsToIdle();
1766 void DesktopLOKTest::testInput()
1768 // Load a Writer document, enable change recording and press a key.
1769 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1770 uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
1772 Scheduler::ProcessEventsToIdle(); // Get focus & other bits setup.
1774 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "far");
1775 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "far");
1776 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, " ");
1777 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, " ");
1778 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "beyond");
1779 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "beyond");
1780 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, " ");
1781 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, " ");
1782 // Mis-spelled ...
1783 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "kovely");
1784 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "kovely");
1785 // Remove it again
1786 pDocument->pClass->removeTextContext(pDocument, 0, 6, 0);
1787 // Replace it with lovely
1788 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "lovely");
1789 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "lovely");
1790 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, " ");
1791 pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, " ");
1793 // get the text ...
1794 pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false);
1795 Scheduler::ProcessEventsToIdle();
1796 char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr);
1797 CPPUNIT_ASSERT(pText != nullptr);
1798 OString aLovely("far beyond lovely ");
1799 CPPUNIT_ASSERT_EQUAL(aLovely, OString(pText));
1800 free(pText);
1803 void DesktopLOKTest::testRedlineWriter()
1805 // Load a Writer document, enable change recording and press a key.
1806 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1807 uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
1808 xPropertySet->setPropertyValue("RecordChanges", uno::makeAny(true));
1809 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0);
1810 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't', 0);
1811 Scheduler::ProcessEventsToIdle();
1813 // Get redline info.
1814 boost::property_tree::ptree aTree;
1815 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:AcceptTrackedChanges");
1816 std::stringstream aStream(pJSON);
1817 free(pJSON);
1818 CPPUNIT_ASSERT(!aStream.str().empty());
1819 boost::property_tree::read_json(aStream, aTree);
1820 // Make sure that pressing a key creates exactly one redline.
1821 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aTree.get_child("redlines").size());
1823 for (boost::property_tree::ptree::value_type& rRedline : aTree.get_child("redlines"))
1824 // This failed with boost::property_tree::ptree_bad_path, as there were no description field.
1825 CPPUNIT_ASSERT_EQUAL(std::string("Insert \xE2\x80\x9Ct\xE2\x80\x9D"), rRedline.second.get<std::string>("description"));
1826 // U+201C LEFT DOUBLE QUOTATION MARK, U+201D RIGHT DOUBLE QUOTATION
1827 // MARK
1830 void DesktopLOKTest::testRedlineCalc()
1832 // Load a Writer document, enable change recording and press a key.
1833 LibLODocument_Impl* pDocument = loadDoc("sheets.ods");
1834 uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
1835 xPropertySet->setPropertyValue("RecordChanges", uno::makeAny(true));
1836 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0);
1837 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't', 0);
1838 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN);
1839 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, KEY_RETURN);
1840 Scheduler::ProcessEventsToIdle();
1842 // Get redline info.
1843 boost::property_tree::ptree aTree;
1844 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:AcceptTrackedChanges");
1845 std::stringstream aStream(pJSON);
1846 free(pJSON);
1847 CPPUNIT_ASSERT(!aStream.str().empty());
1848 boost::property_tree::read_json(aStream, aTree);
1849 // Make sure that pressing a key creates exactly one redline.
1850 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aTree.get_child("redlines").size());
1852 for (boost::property_tree::ptree::value_type& rRedline : aTree.get_child("redlines"))
1853 // This failed with boost::property_tree::ptree_bad_path, as there were no description field.
1854 CPPUNIT_ASSERT_EQUAL(std::string("Cell B4 changed from '5' to 't'"), rRedline.second.get<std::string>("description"));
1857 class ViewCallback
1859 public:
1860 OString m_aCellFormula;
1861 bool m_bTilesInvalidated;
1862 tools::Rectangle m_aOwnCursor;
1863 boost::property_tree::ptree m_aCommentCallbackResult;
1865 ViewCallback()
1866 : m_bTilesInvalidated(false)
1870 static void callback(int nType, const char* pPayload, void* pData)
1872 static_cast<ViewCallback*>(pData)->callbackImpl(nType, pPayload);
1875 void callbackImpl(int nType, const char* pPayload)
1877 OString aPayload(pPayload);
1878 switch (nType)
1880 case LOK_CALLBACK_INVALIDATE_TILES:
1882 m_bTilesInvalidated = true;
1884 break;
1885 case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
1887 uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::fromUtf8(aPayload));
1888 if (OString("EMPTY") == pPayload)
1889 return;
1890 CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(4), aSeq.getLength());
1891 m_aOwnCursor.setX(aSeq[0].toInt32());
1892 m_aOwnCursor.setY(aSeq[1].toInt32());
1893 m_aOwnCursor.setWidth(aSeq[2].toInt32());
1894 m_aOwnCursor.setHeight(aSeq[3].toInt32());
1896 break;
1897 case LOK_CALLBACK_COMMENT:
1899 m_aCommentCallbackResult.clear();
1900 std::stringstream aStream(pPayload);
1901 boost::property_tree::read_json(aStream, m_aCommentCallbackResult);
1902 m_aCommentCallbackResult = m_aCommentCallbackResult.get_child("comment");
1904 break;
1905 case LOK_CALLBACK_CELL_FORMULA:
1907 m_aCellFormula = aPayload;
1909 break;
1914 void DesktopLOKTest::testPaintPartTile()
1916 // Load an impress doc of 2 slides.
1917 // ViewCallback aView1;
1918 // ViewCallback aView2;
1919 LibLODocument_Impl* pDocument = loadDoc("2slides.odp");
1920 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
1921 // pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView1);
1922 int nView1 = pDocument->m_pDocumentClass->getView(pDocument);
1924 // Create a second view.
1925 pDocument->m_pDocumentClass->createView(pDocument);
1926 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
1927 // pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView2);
1929 // Go to the second slide in the second view.
1930 pDocument->m_pDocumentClass->setPart(pDocument, 1);
1932 // Switch back to the first view and start typing.
1933 pDocument->m_pDocumentClass->setView(pDocument, nView1);
1934 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, awt::Key::TAB);
1935 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, awt::Key::TAB);
1936 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'x', 0);
1937 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'x', 0);
1938 Scheduler::ProcessEventsToIdle();
1940 // Call paintPartTile() to paint the second part (in whichever view it finds suitable for this).
1941 unsigned char pPixels[256 * 256 * 4];
1942 pDocument->m_pDocumentClass->paintPartTile(pDocument, pPixels, 1, 256, 256, 0, 0, 256, 256);
1944 // Type again.
1945 Scheduler::ProcessEventsToIdle();
1946 // aView1.m_bTilesInvalidated = false;
1947 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'x', 0);
1948 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'x', 0);
1949 Scheduler::ProcessEventsToIdle();
1950 // This failed: paintPartTile() (as a side-effect) ended the text edit of
1951 // the first view, so there were no invalidations.
1952 //CPPUNIT_ASSERT(aView1.m_bTilesInvalidated);
1955 void DesktopLOKTest::testWriterCommentInsertCursor()
1957 // Load a document and type a character into the body text of the second view.
1958 ViewCallback aView1;
1959 ViewCallback aView2;
1960 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1961 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
1962 pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView1);
1963 pDocument->m_pDocumentClass->createView(pDocument);
1964 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
1965 pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView2);
1966 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'x', 0);
1967 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'x', 0);
1968 Scheduler::ProcessEventsToIdle();
1969 tools::Rectangle aBodyCursor = aView2.m_aOwnCursor;
1971 // Now insert a comment and make sure that the comment's cursor is shown,
1972 // not the body text's one.
1973 aView1.m_aOwnCursor.SetEmpty();
1974 const int nCtrlAltC = KEY_MOD1 + KEY_MOD2 + 512 + 'c' - 'a';
1975 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'c', nCtrlAltC);
1976 pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'c', nCtrlAltC);
1977 Scheduler::ProcessEventsToIdle();
1978 // Wait for SfxBindings to actually update the state, which updated the
1979 // cursor as well.
1980 osl::Thread::wait(std::chrono::seconds(1));
1981 Scheduler::ProcessEventsToIdle();
1982 // This failed: the body cursor was shown right after inserting a comment.
1983 CPPUNIT_ASSERT(aView2.m_aOwnCursor.getX() > aBodyCursor.getX());
1984 // This failed, the first view's cursor also jumped when the second view
1985 // inserted the comment.
1986 CPPUNIT_ASSERT(aView1.m_aOwnCursor.IsEmpty());
1988 Scheduler::ProcessEventsToIdle();
1989 pDocument->m_pDocumentClass->registerCallback(pDocument, nullptr, reinterpret_cast<void*>(1));
1992 #if HAVE_MORE_FONTS
1993 void DesktopLOKTest::testGetFontSubset()
1995 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
1996 OUString aFontName = rtl::Uri::encode(
1997 OUString("Liberation Sans"),
1998 rtl_UriCharClassRelSegment,
1999 rtl_UriEncodeKeepEscapes,
2000 RTL_TEXTENCODING_UTF8
2002 OString aCommand = OUStringToOString(".uno:FontSubset&name=" + aFontName, RTL_TEXTENCODING_UTF8);
2003 boost::property_tree::ptree aTree;
2004 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aCommand.getStr());
2005 std::stringstream aStream(pJSON);
2006 boost::property_tree::read_json(aStream, aTree);
2007 CPPUNIT_ASSERT( !aTree.empty() );
2008 CPPUNIT_ASSERT_EQUAL( std::string(".uno:FontSubset"), aTree.get_child("commandName").get_value<std::string>() );
2009 boost::property_tree::ptree aValues = aTree.get_child("commandValues");
2010 CPPUNIT_ASSERT( !aValues.empty() );
2011 free(pJSON);
2013 #endif
2015 void DesktopLOKTest::testCommentsWriter()
2017 // Disable tiled rendering for comments
2018 comphelper::LibreOfficeKit::setTiledAnnotations(false);
2020 LibLODocument_Impl* pDocument = loadDoc("comments.odt");
2021 pDocument->m_pDocumentClass->initializeForRendering(pDocument, nullptr);
2022 long nWidth, nHeight;
2023 pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight);
2025 // Document width alongwith without sidebar comes to be < 13000
2026 CPPUNIT_ASSERT( nWidth < 13000 );
2028 // Can we get all the comments using .uno:ViewAnnotations command ?
2029 boost::property_tree::ptree aTree;
2030 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations");
2031 std::stringstream aStream(pJSON);
2032 free(pJSON);
2033 CPPUNIT_ASSERT(!aStream.str().empty());
2034 boost::property_tree::read_json(aStream, aTree);
2035 // There are 3 comments in the document already
2036 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aTree.get_child("comments").size());
2038 int nComment2Id = 0;
2039 // Check if all comment fields have valid data
2040 for (const auto& rComment : aTree.get_child("comments"))
2042 CPPUNIT_ASSERT(rComment.second.get<int>("id") > 0);
2043 CPPUNIT_ASSERT(!rComment.second.get<std::string>("author").empty());
2044 CPPUNIT_ASSERT(!rComment.second.get<std::string>("text").empty());
2045 // Has a valid iso 8601 date time string
2046 css::util::DateTime aDateTime;
2047 OUString aDateTimeString = OUString::createFromAscii(rComment.second.get<std::string>("dateTime").c_str());
2048 CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime));
2050 // This comment has a marked text range
2051 if (rComment.second.get<std::string>("text") == "Comment 2")
2053 CPPUNIT_ASSERT(!rComment.second.get<std::string>("textRange").empty());
2054 nComment2Id = rComment.second.get<int>("id");
2056 // This is a reply comment
2057 else if (rComment.second.get<std::string>("text") == "Reply to Comment 2")
2059 CPPUNIT_ASSERT_EQUAL(nComment2Id, rComment.second.get<int>("parent"));
2063 comphelper::LibreOfficeKit::setTiledAnnotations(true);
2067 void DesktopLOKTest::testCommentsCalc()
2069 // Disable tiled rendering for comments
2070 comphelper::LibreOfficeKit::setTiledAnnotations(false);
2072 LibLODocument_Impl* pDocument = loadDoc("sheets.ods");
2073 pDocument->m_pDocumentClass->initializeForRendering(pDocument, nullptr);
2075 // Can we get all the comments using .uno:ViewAnnotations command ?
2076 boost::property_tree::ptree aTree;
2077 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations");
2078 std::stringstream aStream(pJSON);
2079 free(pJSON);
2080 CPPUNIT_ASSERT(!aStream.str().empty());
2081 boost::property_tree::read_json(aStream, aTree);
2082 // There are 2 comments in the document already
2083 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aTree.get_child("comments").size());
2085 // Check if all comment fields have valid data
2086 int nIdx = 0;
2087 for (const auto& rComment : aTree.get_child("comments"))
2089 switch(nIdx)
2091 case 0:
2093 CPPUNIT_ASSERT_EQUAL(std::string("4"), rComment.second.get<std::string>("tab"));
2094 CPPUNIT_ASSERT_EQUAL(std::string("Comment1"), rComment.second.get<std::string>("text"));
2095 CPPUNIT_ASSERT_EQUAL(std::string("7650, 3570, 1274, 254"), rComment.second.get<std::string>("cellPos"));
2097 break;
2098 case 1:
2100 CPPUNIT_ASSERT_EQUAL(std::string("4"), rComment.second.get<std::string>("tab"));
2101 CPPUNIT_ASSERT_EQUAL(std::string("Comment2"), rComment.second.get<std::string>("text"));
2102 CPPUNIT_ASSERT_EQUAL(std::string("8925, 4335, 1274, 254"), rComment.second.get<std::string>("cellPos"));
2104 break;
2107 ++nIdx;
2110 // We checked all the comments
2111 CPPUNIT_ASSERT_EQUAL(2, nIdx);
2113 comphelper::LibreOfficeKit::setTiledAnnotations(true);
2117 void DesktopLOKTest::testCommentsImpress()
2119 // Disable tiled rendering for comments
2120 comphelper::LibreOfficeKit::setTiledAnnotations(false);
2122 LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp");
2123 pDocument->m_pDocumentClass->initializeForRendering(pDocument, nullptr);
2125 // Can we get all the comments using .uno:ViewAnnotations command ?
2126 boost::property_tree::ptree aTree;
2127 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations");
2128 std::stringstream aStream(pJSON);
2129 free(pJSON);
2130 CPPUNIT_ASSERT(!aStream.str().empty());
2131 boost::property_tree::read_json(aStream, aTree);
2132 // There are 2 comments in the document already
2133 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aTree.get_child("comments").size());
2135 // Check if all comment fields have valid data
2136 int nIdx = 0;
2137 for (const auto& rComment : aTree.get_child("comments"))
2139 switch(nIdx)
2141 case 0:
2143 CPPUNIT_ASSERT(rComment.second.get<int>("id") > 0);
2144 CPPUNIT_ASSERT_EQUAL(std::string("This is comment1"), rComment.second.get<std::string>("text"));
2145 CPPUNIT_ASSERT_EQUAL(std::string("LOK User1"), rComment.second.get<std::string>("author"));
2146 css::util::DateTime aDateTime;
2147 OUString aDateTimeString = OUString::createFromAscii(rComment.second.get<std::string>("dateTime").c_str());
2148 CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime));
2150 break;
2151 case 1:
2153 CPPUNIT_ASSERT(rComment.second.get<int>("id") > 0);
2154 CPPUNIT_ASSERT_EQUAL(std::string("This is comment2"), rComment.second.get<std::string>("text"));
2155 CPPUNIT_ASSERT_EQUAL(std::string("LOK User2"), rComment.second.get<std::string>("author"));
2156 css::util::DateTime aDateTime;
2157 OUString aDateTimeString = OUString::createFromAscii(rComment.second.get<std::string>("dateTime").c_str());
2158 CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime));
2160 break;
2163 ++nIdx;
2166 // We checked all the comments
2167 CPPUNIT_ASSERT_EQUAL(2, nIdx);
2169 comphelper::LibreOfficeKit::setTiledAnnotations(true);
2172 void DesktopLOKTest::testCommentsCallbacksWriter()
2174 // Comments callback are emitted only if tiled annotations are off
2175 comphelper::LibreOfficeKit::setTiledAnnotations(false);
2176 ViewCallback aView1;
2177 ViewCallback aView2;
2178 LibLODocument_Impl* pDocument = loadDoc("comments.odt");
2179 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2180 pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView1);
2181 pDocument->m_pDocumentClass->createView(pDocument);
2182 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2183 pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView2);
2185 // Add a new comment
2186 OString aCommandArgs("{ \"Text\": { \"type\": \"string\", \"value\": \"Additional comment\" }, \"Author\": { \"type\": \"string\", \"value\": \"LOK User1\" } }");
2187 pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", aCommandArgs.getStr(), false);
2188 Scheduler::ProcessEventsToIdle();
2190 // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action
2191 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
2192 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
2193 int nCommentId1 = aView1.m_aCommentCallbackResult.get<int>("id");
2195 // Reply to a comment just added
2196 aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId1) + "\" }, \"Text\": { \"type\": \"string\", \"value\": \"Reply comment\" } }";
2197 pDocument->pClass->postUnoCommand(pDocument, ".uno:ReplyComment", aCommandArgs.getStr(), false);
2198 Scheduler::ProcessEventsToIdle();
2200 // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action and linked to its parent comment
2201 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
2202 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
2203 CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("parent"));
2204 CPPUNIT_ASSERT_EQUAL(nCommentId1, aView2.m_aCommentCallbackResult.get<int>("parent"));
2205 CPPUNIT_ASSERT_EQUAL(std::string("Reply comment"), aView1.m_aCommentCallbackResult.get<std::string>("text"));
2206 CPPUNIT_ASSERT_EQUAL(std::string("Reply comment"), aView2.m_aCommentCallbackResult.get<std::string>("text"));
2207 int nCommentId2 = aView1.m_aCommentCallbackResult.get<int>("id");
2209 // Edit the previously added comment
2210 aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId2) + "\" }, \"Text\": { \"type\": \"string\", \"value\": \"Edited comment\" } }";
2211 pDocument->pClass->postUnoCommand(pDocument, ".uno:EditAnnotation", aCommandArgs.getStr(), false);
2212 Scheduler::ProcessEventsToIdle();
2214 // We received a LOK_CALLBACK_COMMENT callback with comment 'Modify' action
2215 CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
2216 CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
2217 // parent is unchanged still
2218 CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("parent"));
2219 CPPUNIT_ASSERT_EQUAL(nCommentId1, aView2.m_aCommentCallbackResult.get<int>("parent"));
2220 CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView1.m_aCommentCallbackResult.get<std::string>("text"));
2221 CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView2.m_aCommentCallbackResult.get<std::string>("text"));
2223 // Delete the reply comment just added
2224 aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId2) + "\" } }";
2225 pDocument->pClass->postUnoCommand(pDocument, ".uno:DeleteComment", aCommandArgs.getStr(), false);
2226 Scheduler::ProcessEventsToIdle();
2228 // We received a LOK_CALLBACK_COMMENT callback with comment 'Remove' action
2229 CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
2230 CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
2231 CPPUNIT_ASSERT_EQUAL(nCommentId2, aView1.m_aCommentCallbackResult.get<int>("id"));
2232 CPPUNIT_ASSERT_EQUAL(nCommentId2, aView2.m_aCommentCallbackResult.get<int>("id"));
2234 // Reply to nCommentId1 again
2235 aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId1) + "\" }, \"Text\": { \"type\": \"string\", \"value\": \"Reply comment again\" } }";
2236 pDocument->pClass->postUnoCommand(pDocument, ".uno:ReplyComment", aCommandArgs.getStr(), false);
2237 Scheduler::ProcessEventsToIdle();
2239 // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action and linked to its parent comment
2240 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
2241 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
2242 CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("parent"));
2243 CPPUNIT_ASSERT_EQUAL(nCommentId1, aView2.m_aCommentCallbackResult.get<int>("parent"));
2244 CPPUNIT_ASSERT_EQUAL(std::string("Reply comment again"), aView1.m_aCommentCallbackResult.get<std::string>("text"));
2245 CPPUNIT_ASSERT_EQUAL(std::string("Reply comment again"), aView2.m_aCommentCallbackResult.get<std::string>("text"));
2247 // .uno:ViewAnnotations returns total of 5 comments
2248 boost::property_tree::ptree aTree;
2249 char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations");
2250 std::stringstream aStream(pJSON);
2251 free(pJSON);
2252 CPPUNIT_ASSERT(!aStream.str().empty());
2253 boost::property_tree::read_json(aStream, aTree);
2254 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(5), aTree.get_child("comments").size());
2256 pDocument->m_pDocumentClass->registerCallback(pDocument, nullptr, reinterpret_cast<void*>(1));
2259 void DesktopLOKTest::testRunMacro()
2261 LibLibreOffice_Impl aOffice;
2262 bool bGoodMacro, bNonExistentMacro;
2264 // Tools macros come pre-installed in system share/basic folder,
2265 bGoodMacro = aOffice.m_pOfficeClass->runMacro(&aOffice, OString("macro:///Tools.Debug.ActivateReadOnlyFlag()").getStr());
2266 CPPUNIT_ASSERT(bGoodMacro);
2268 bNonExistentMacro = aOffice.m_pOfficeClass->runMacro(&aOffice, OString("macro:///I.Am.Not(There)").getStr());
2269 CPPUNIT_ASSERT(!bNonExistentMacro);
2272 void DesktopLOKTest::testExtractParameter()
2274 OUString aOptions("Language=de-DE");
2275 OUString aValue = extractParameter(aOptions, "Language");
2276 CPPUNIT_ASSERT_EQUAL(OUString("de-DE"), aValue);
2277 CPPUNIT_ASSERT_EQUAL(OUString(), aOptions);
2279 aOptions = "Language=en-US,Something";
2280 aValue = extractParameter(aOptions, "Language");
2281 CPPUNIT_ASSERT_EQUAL(OUString("en-US"), aValue);
2282 CPPUNIT_ASSERT_EQUAL(OUString("Something"), aOptions);
2284 aOptions = "SomethingElse,Language=cs-CZ";
2285 aValue = extractParameter(aOptions, "Language");
2286 CPPUNIT_ASSERT_EQUAL(OUString("cs-CZ"), aValue);
2287 CPPUNIT_ASSERT_EQUAL(OUString("SomethingElse"), aOptions);
2289 aOptions = "Something1,Language=hu-HU,Something2";
2290 aValue = extractParameter(aOptions, "Language");
2291 CPPUNIT_ASSERT_EQUAL(OUString("hu-HU"), aValue);
2292 CPPUNIT_ASSERT_EQUAL(OUString("Something1,Something2"), aOptions);
2294 aOptions = "Something1,Something2=blah,Something3";
2295 aValue = extractParameter(aOptions, "Language");
2296 CPPUNIT_ASSERT_EQUAL(OUString(), aValue);
2297 CPPUNIT_ASSERT_EQUAL(OUString("Something1,Something2=blah,Something3"), aOptions);
2300 void DesktopLOKTest::readFileIntoByteVector(OUString const & sFilename, std::vector<unsigned char> & rByteVector)
2302 rByteVector.clear();
2303 OUString aURL;
2304 createFileURL(sFilename, aURL);
2305 SvFileStream aStream(aURL, StreamMode::READ);
2306 rByteVector.resize(aStream.remainingSize());
2307 aStream.ReadBytes(rByteVector.data(), aStream.remainingSize());
2310 void DesktopLOKTest::testGetSignatureState_Signed()
2312 LibLODocument_Impl* pDocument = loadDoc("signed.odt");
2313 Scheduler::ProcessEventsToIdle();
2314 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2315 int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument);
2316 CPPUNIT_ASSERT_EQUAL(int(4), nState);
2318 std::vector<unsigned char> aCertificate;
2320 readFileIntoByteVector("rootCA.der", aCertificate);
2321 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2322 pDocument, aCertificate.data(), int(aCertificate.size()));
2323 CPPUNIT_ASSERT(bResult);
2327 readFileIntoByteVector("intermediateRootCA.der", aCertificate);
2328 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2329 pDocument, aCertificate.data(), int(aCertificate.size()));
2330 CPPUNIT_ASSERT(bResult);
2333 nState = pDocument->m_pDocumentClass->getSignatureState(pDocument);
2334 CPPUNIT_ASSERT_EQUAL(int(1), nState);
2337 void DesktopLOKTest::testGetSignatureState_NonSigned()
2339 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
2340 Scheduler::ProcessEventsToIdle();
2341 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2342 int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument);
2343 CPPUNIT_ASSERT_EQUAL(int(0), nState);
2346 void DesktopLOKTest::testInsertCertificate_DER_ODT()
2348 // Load the document, save it into a temp file and load that file again
2349 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
2350 utl::TempFile aTempFile;
2351 aTempFile.EnableKillingFile();
2352 CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, aTempFile.GetURL().toUtf8().getStr(), "odt", nullptr));
2353 closeDoc();
2355 mxComponent = loadFromDesktop(aTempFile.GetURL(), "com.sun.star.text.TextDocument");
2356 pDocument = new LibLODocument_Impl(mxComponent);
2358 Scheduler::ProcessEventsToIdle();
2359 CPPUNIT_ASSERT(mxComponent.is());
2360 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2361 Scheduler::ProcessEventsToIdle();
2363 std::vector<unsigned char> aCertificate;
2364 std::vector<unsigned char> aPrivateKey;
2367 readFileIntoByteVector("rootCA.der", aCertificate);
2369 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2370 pDocument, aCertificate.data(), int(aCertificate.size()));
2371 CPPUNIT_ASSERT(bResult);
2375 readFileIntoByteVector("intermediateRootCA.der", aCertificate);
2377 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2378 pDocument, aCertificate.data(), int(aCertificate.size()));
2379 CPPUNIT_ASSERT(bResult);
2383 readFileIntoByteVector("certificate.der", aCertificate);
2384 readFileIntoByteVector("certificatePrivateKey.der", aPrivateKey);
2386 bool bResult = pDocument->m_pDocumentClass->insertCertificate(pDocument,
2387 aCertificate.data(), int(aCertificate.size()),
2388 aPrivateKey.data(), int(aPrivateKey.size()));
2389 CPPUNIT_ASSERT(bResult);
2392 int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument);
2393 CPPUNIT_ASSERT_EQUAL(int(1), nState);
2397 void DesktopLOKTest::testInsertCertificate_PEM_ODT()
2399 // Load the document, save it into a temp file and load that file again
2400 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
2401 utl::TempFile aTempFile;
2402 aTempFile.EnableKillingFile();
2403 CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, aTempFile.GetURL().toUtf8().getStr(), "odt", nullptr));
2404 closeDoc();
2406 mxComponent = loadFromDesktop(aTempFile.GetURL(), "com.sun.star.text.TextDocument");
2407 pDocument = new LibLODocument_Impl(mxComponent);
2409 Scheduler::ProcessEventsToIdle();
2410 CPPUNIT_ASSERT(mxComponent.is());
2411 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2412 Scheduler::ProcessEventsToIdle();
2414 std::vector<unsigned char> aCertificate;
2415 std::vector<unsigned char> aPrivateKey;
2418 readFileIntoByteVector("test-cert-chain-1.pem", aCertificate);
2420 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2421 pDocument, aCertificate.data(), int(aCertificate.size()));
2422 CPPUNIT_ASSERT(bResult);
2426 readFileIntoByteVector("test-cert-chain-2.pem", aCertificate);
2428 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2429 pDocument, aCertificate.data(), int(aCertificate.size()));
2430 CPPUNIT_ASSERT(bResult);
2434 readFileIntoByteVector("test-cert-chain-3.pem", aCertificate);
2436 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2437 pDocument, aCertificate.data(), int(aCertificate.size()));
2438 CPPUNIT_ASSERT(bResult);
2442 readFileIntoByteVector("test-cert-signing.pem", aCertificate);
2443 readFileIntoByteVector("test-PK-signing.pem", aPrivateKey);
2445 bool bResult = pDocument->m_pDocumentClass->insertCertificate(pDocument,
2446 aCertificate.data(), int(aCertificate.size()),
2447 aPrivateKey.data(), int(aPrivateKey.size()));
2448 CPPUNIT_ASSERT(bResult);
2451 int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument);
2452 CPPUNIT_ASSERT_EQUAL(int(1), nState);
2455 void DesktopLOKTest::testInsertCertificate_PEM_DOCX()
2457 // Load the document, save it into a temp file and load that file again
2458 LibLODocument_Impl* pDocument = loadDoc("blank_text.docx");
2459 utl::TempFile aTempFile;
2460 aTempFile.EnableKillingFile();
2461 CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, aTempFile.GetURL().toUtf8().getStr(), "docx", nullptr));
2462 closeDoc();
2464 mxComponent = loadFromDesktop(aTempFile.GetURL(), "com.sun.star.text.TextDocument");
2465 pDocument = new LibLODocument_Impl(mxComponent);
2467 Scheduler::ProcessEventsToIdle();
2468 CPPUNIT_ASSERT(mxComponent.is());
2469 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2470 Scheduler::ProcessEventsToIdle();
2472 std::vector<unsigned char> aCertificate;
2473 std::vector<unsigned char> aPrivateKey;
2476 readFileIntoByteVector("test-cert-chain-1.pem", aCertificate);
2478 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2479 pDocument, aCertificate.data(), int(aCertificate.size()));
2480 CPPUNIT_ASSERT(bResult);
2484 readFileIntoByteVector("test-cert-chain-2.pem", aCertificate);
2486 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2487 pDocument, aCertificate.data(), int(aCertificate.size()));
2488 CPPUNIT_ASSERT(bResult);
2492 readFileIntoByteVector("test-cert-chain-3.pem", aCertificate);
2494 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2495 pDocument, aCertificate.data(), int(aCertificate.size()));
2496 CPPUNIT_ASSERT(bResult);
2500 readFileIntoByteVector("test-cert-signing.pem", aCertificate);
2501 readFileIntoByteVector("test-PK-signing.pem", aPrivateKey);
2503 bool bResult = pDocument->m_pDocumentClass->insertCertificate(pDocument,
2504 aCertificate.data(), int(aCertificate.size()),
2505 aPrivateKey.data(), int(aPrivateKey.size()));
2506 CPPUNIT_ASSERT(bResult);
2509 int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument);
2510 CPPUNIT_ASSERT_EQUAL(int(5), nState);
2513 void DesktopLOKTest::testSignDocument_PEM_PDF()
2515 // FIXME: needs investigation ...
2516 return;
2518 // Load the document, save it into a temp file and load that file again
2519 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
2520 utl::TempFile aTempFile;
2521 aTempFile.EnableKillingFile();
2523 Scheduler::ProcessEventsToIdle();
2524 CPPUNIT_ASSERT(mxComponent.is());
2525 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2526 Scheduler::ProcessEventsToIdle();
2528 std::vector<unsigned char> aCertificate;
2529 std::vector<unsigned char> aPrivateKey;
2532 readFileIntoByteVector("test-cert-chain-1.pem", aCertificate);
2534 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2535 pDocument, aCertificate.data(), int(aCertificate.size()));
2536 CPPUNIT_ASSERT(bResult);
2540 readFileIntoByteVector("test-cert-chain-2.pem", aCertificate);
2542 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2543 pDocument, aCertificate.data(), int(aCertificate.size()));
2544 CPPUNIT_ASSERT(bResult);
2548 readFileIntoByteVector("test-cert-chain-3.pem", aCertificate);
2550 bool bResult = pDocument->m_pDocumentClass->addCertificate(
2551 pDocument, aCertificate.data(), int(aCertificate.size()));
2552 CPPUNIT_ASSERT(bResult);
2555 CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, aTempFile.GetURL().toUtf8().getStr(), "pdf", nullptr));
2557 closeDoc();
2559 Scheduler::ProcessEventsToIdle();
2561 readFileIntoByteVector("test-cert-signing.pem", aCertificate);
2562 readFileIntoByteVector("test-PK-signing.pem", aPrivateKey);
2564 LibLibreOffice_Impl aOffice;
2565 bool bResult = aOffice.m_pOfficeClass->signDocument(&aOffice, aTempFile.GetURL().toUtf8().getStr(),
2566 aCertificate.data(), int(aCertificate.size()),
2567 aPrivateKey.data(), int(aPrivateKey.size()));
2569 CPPUNIT_ASSERT(bResult);
2572 void DesktopLOKTest::testTextSelectionHandles()
2574 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
2575 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
2577 OString aText("hello");
2578 CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8", aText.getStr(), aText.getLength()));
2580 // select the inserted text
2581 pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false);
2582 Scheduler::ProcessEventsToIdle();
2583 char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr);
2584 CPPUNIT_ASSERT_EQUAL(aText, OString(pText));
2585 free(pText);
2586 CPPUNIT_ASSERT_EQUAL(OString("1418, 1418, 0, 275"), m_aTextSelectionStart);
2587 CPPUNIT_ASSERT_EQUAL(OString("1898, 1418, 0, 275"), m_aTextSelectionEnd);
2589 // deselect & check
2590 m_aTextSelectionStart = "";
2591 m_aTextSelectionEnd = "";
2592 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, com::sun::star::awt::Key::ESCAPE);
2593 Scheduler::ProcessEventsToIdle();
2594 pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr);
2595 CPPUNIT_ASSERT_EQUAL(OString(), OString(pText));
2596 free(pText);
2597 CPPUNIT_ASSERT_EQUAL(OString(), m_aTextSelectionStart);
2598 CPPUNIT_ASSERT_EQUAL(OString(), m_aTextSelectionEnd);
2600 // select again; the positions of the selection handles have to be sent
2601 // again
2602 pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false);
2603 Scheduler::ProcessEventsToIdle();
2604 pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr);
2605 CPPUNIT_ASSERT_EQUAL(aText, OString(pText));
2606 free(pText);
2607 CPPUNIT_ASSERT_EQUAL(OString("1418, 1418, 0, 275"), m_aTextSelectionStart);
2608 CPPUNIT_ASSERT_EQUAL(OString("1898, 1418, 0, 275"), m_aTextSelectionEnd);
2611 void DesktopLOKTest::testDialogPaste()
2613 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
2614 pDocument->pClass->postUnoCommand(pDocument, ".uno:HyperlinkDialog", nullptr, false);
2615 Scheduler::ProcessEventsToIdle();
2617 SfxViewShell* pViewShell = SfxViewShell::Current();
2618 pViewShell->GetViewFrame()->GetBindings().Update();
2620 VclPtr<vcl::Window> pWindow(Application::GetActiveTopWindow());
2621 CPPUNIT_ASSERT(pWindow);
2623 pDocument->pClass->postWindow(pDocument, pWindow->GetLOKWindowId(), LOK_WINDOW_PASTE,
2624 "{ \"MimeType\" : { \"type\" : \"string\", \"value\" : \"text/plain;charset=utf-8\" }, \"Data\" : { \"type\" : \"[]byte\", \"value\" : \"www.softwarelibre.org.bo\" } }");
2625 Scheduler::ProcessEventsToIdle();
2627 Control* pCtrlFocused = GetFocusControl(pWindow.get());
2628 CPPUNIT_ASSERT(pCtrlFocused);
2629 ComboBox* pCtrlURL = dynamic_cast<ComboBox*>(pCtrlFocused);
2630 CPPUNIT_ASSERT(pCtrlURL);
2631 CPPUNIT_ASSERT_EQUAL(OUString("www.softwarelibre.org.bo"), pCtrlURL->GetText());
2633 static_cast<SystemWindow*>(pWindow.get())->Close();
2634 Scheduler::ProcessEventsToIdle();
2637 void DesktopLOKTest::testComplexSelection()
2639 // Start with a blank text file and add contents.
2640 LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
2641 // LibLODocument_Impl* pDocument = loadDoc("sheet_with_image.ods");
2642 static const OString aText("hello world");
2644 // Certainly not complex.
2645 CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionType(pDocument));
2647 // Paste text.
2648 CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8", aText.getStr(), aText.getLength()));
2650 // No selection.
2651 CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionType(pDocument));
2653 // Paste an image.
2654 OUString aFileURL;
2655 createFileURL("paste.jpg", aFileURL);
2656 std::ifstream aImageStream(aFileURL.toUtf8().copy(strlen("file://")).getStr());
2657 std::vector<char> aImageContents((std::istreambuf_iterator<char>(aImageStream)), std::istreambuf_iterator<char>());
2658 CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "image/jpeg", aImageContents.data(), aImageContents.size()));
2660 // Now select-all.
2661 pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false);
2662 Scheduler::ProcessEventsToIdle();
2664 // We expect this to be complex.
2665 const int type = pDocument->pClass->getSelectionType(pDocument);
2666 CPPUNIT_ASSERT_EQUAL((int)LOK_SELTYPE_COMPLEX, type);
2668 // Export as plain text, we should get only the text part "hello".
2669 char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr);
2670 CPPUNIT_ASSERT(pText != nullptr);
2671 CPPUNIT_ASSERT_EQUAL(aText, OString(pText));
2672 free(pText);
2674 // Export as rtf, we should also get the image.
2675 pText = pDocument->pClass->getTextSelection(pDocument, "text/rtf", nullptr);
2676 CPPUNIT_ASSERT(pText != nullptr);
2677 CPPUNIT_ASSERT(std::string(pText).find(aText.getStr()) != std::string::npos); // Must have the text.
2678 CPPUNIT_ASSERT(std::string(pText).find("pict{") != std::string::npos); // Must have the image as well.
2679 free(pText);
2681 // Export as html, we should also get the image.
2682 pText = pDocument->pClass->getTextSelection(pDocument, "text/html", nullptr);
2683 CPPUNIT_ASSERT(pText != nullptr);
2684 CPPUNIT_ASSERT(std::string(pText).find(aText.getStr()) != std::string::npos); // Must have the text.
2685 CPPUNIT_ASSERT(std::string(pText).find("<img") != std::string::npos); // Must have the image as well.
2686 free(pText);
2688 // We expect this to be complex.
2689 CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_COMPLEX), pDocument->pClass->getSelectionType(pDocument));
2692 void DesktopLOKTest::testCalcSaveAs()
2694 comphelper::LibreOfficeKit::setActive();
2696 LibLODocument_Impl* pDocument = loadDoc("sheets.ods");
2697 CPPUNIT_ASSERT(pDocument);
2699 // Enter some text, but don't commit.
2700 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'X', 0);
2701 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'X', 0);
2702 Scheduler::ProcessEventsToIdle();
2704 // Save as a new file.
2705 OUString aNewFileUrl = "file:///tmp/saveas.ods";
2706 pDocument->pClass->saveAs(pDocument, aNewFileUrl.toUtf8().getStr(), nullptr, nullptr);
2707 closeDoc();
2709 // Load the new document and verify that the in-flight changes are saved.
2710 pDocument = loadDocUrl(aNewFileUrl, LOK_DOCTYPE_SPREADSHEET);
2711 CPPUNIT_ASSERT(pDocument);
2713 ViewCallback aView;
2714 pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}");
2715 pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView);
2717 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT);
2718 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT);
2719 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, KEY_LEFT);
2720 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, KEY_LEFT);
2721 Scheduler::ProcessEventsToIdle();
2723 CPPUNIT_ASSERT_EQUAL(OString("X"), aView.m_aCellFormula);
2726 void DesktopLOKTest::testSpellcheckerMultiView()
2728 static const OUString aLangISO("en-US");
2729 SvtSysLocaleOptions aSysLocaleOptions;
2730 aSysLocaleOptions.SetLocaleConfigString(aLangISO);
2731 aSysLocaleOptions.SetUILocaleConfigString(aLangISO);
2732 comphelper::LibreOfficeKit::setLanguageTag(aLangISO, true);
2734 auto aSavedSettings = Application::GetSettings();
2735 std::unique_ptr<Resetter> pResetter(
2736 new Resetter([&]() { Application::SetSettings(aSavedSettings); }));
2737 AllSettings aSettings(aSavedSettings);
2738 aSettings.SetLanguageTag(aLangISO, true);
2739 Application::SetSettings(aSettings);
2741 LibLODocument_Impl* pDocument = loadDoc("sheet_with_image.ods", LOK_DOCTYPE_SPREADSHEET);
2742 pDocument->pClass->setViewLanguage(pDocument, 0, "en-US"); // For spellchecking.
2743 pDocument->pClass->initializeForRendering(pDocument, nullptr);
2744 pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
2746 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0);
2747 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0);
2748 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0);
2749 pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, com::sun::star::awt::Key::ESCAPE);
2751 // Start spellchecking.
2752 pDocument->pClass->postUnoCommand(pDocument, ".uno:SpellDialog", nullptr, false);
2754 // Uncommenting this will result in a deadlock.
2755 // Because the language configuration above is not effective, and no
2756 // language is actually set, the spell-dialog finds no misspelled
2757 // words, and displays a message box, which must be dismissed to
2758 // continue.
2759 // Need to fix the language configuration issue to enable this.
2760 // Scheduler::ProcessEventsToIdle();
2762 CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument));
2764 // Now create another view.
2765 const int nViewId = pDocument->m_pDocumentClass->createView(pDocument);
2766 CPPUNIT_ASSERT_EQUAL(2, pDocument->m_pDocumentClass->getViewsCount(pDocument));
2768 // And destroy it.
2769 pDocument->m_pDocumentClass->destroyView(pDocument, nViewId);
2771 // We should survive the destroyed view.
2772 CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument));
2775 namespace {
2777 constexpr size_t classOffset(int i)
2779 return sizeof(static_cast<struct _LibreOfficeKitClass*>(nullptr)->nSize) + i * sizeof(void*);
2782 constexpr size_t documentClassOffset(int i)
2784 return sizeof(static_cast<struct _LibreOfficeKitDocumentClass*>(nullptr)->nSize) + i * sizeof(void*);
2789 void DesktopLOKTest::testABI()
2791 // STABLE ABI, NEVER CHANGE (unless there's a very good reason, agreed by ESC, etc.)
2792 CPPUNIT_ASSERT_EQUAL(classOffset(0), offsetof(struct _LibreOfficeKitClass, destroy));
2793 CPPUNIT_ASSERT_EQUAL(classOffset(1), offsetof(struct _LibreOfficeKitClass, documentLoad));
2794 CPPUNIT_ASSERT_EQUAL(classOffset(2), offsetof(struct _LibreOfficeKitClass, getError));
2795 CPPUNIT_ASSERT_EQUAL(classOffset(3), offsetof(struct _LibreOfficeKitClass, documentLoadWithOptions));
2796 CPPUNIT_ASSERT_EQUAL(classOffset(4), offsetof(struct _LibreOfficeKitClass, freeError));
2797 CPPUNIT_ASSERT_EQUAL(classOffset(5), offsetof(struct _LibreOfficeKitClass, registerCallback));
2798 CPPUNIT_ASSERT_EQUAL(classOffset(6), offsetof(struct _LibreOfficeKitClass, getFilterTypes));
2799 CPPUNIT_ASSERT_EQUAL(classOffset(7), offsetof(struct _LibreOfficeKitClass, setOptionalFeatures));
2800 CPPUNIT_ASSERT_EQUAL(classOffset(8), offsetof(struct _LibreOfficeKitClass, setDocumentPassword));
2801 CPPUNIT_ASSERT_EQUAL(classOffset(9), offsetof(struct _LibreOfficeKitClass, getVersionInfo));
2802 CPPUNIT_ASSERT_EQUAL(classOffset(10), offsetof(struct _LibreOfficeKitClass, runMacro));
2803 CPPUNIT_ASSERT_EQUAL(classOffset(11), offsetof(struct _LibreOfficeKitClass, signDocument));
2805 CPPUNIT_ASSERT_EQUAL(documentClassOffset(0), offsetof(struct _LibreOfficeKitDocumentClass, destroy));
2806 CPPUNIT_ASSERT_EQUAL(documentClassOffset(1), offsetof(struct _LibreOfficeKitDocumentClass, saveAs));
2808 // Unstable ABI, but still think twice before changing this
2809 // Eg. can't you add your new member at the end of the struct instead of
2810 // in the middle? The thing you are changing - is it already part of some
2811 // release?
2812 CPPUNIT_ASSERT_EQUAL(documentClassOffset(2), offsetof(struct _LibreOfficeKitDocumentClass, getDocumentType));
2813 CPPUNIT_ASSERT_EQUAL(documentClassOffset(3), offsetof(struct _LibreOfficeKitDocumentClass, getParts));
2814 CPPUNIT_ASSERT_EQUAL(documentClassOffset(4), offsetof(struct _LibreOfficeKitDocumentClass, getPartPageRectangles));
2815 CPPUNIT_ASSERT_EQUAL(documentClassOffset(5), offsetof(struct _LibreOfficeKitDocumentClass, getPart));
2816 CPPUNIT_ASSERT_EQUAL(documentClassOffset(6), offsetof(struct _LibreOfficeKitDocumentClass, setPart));
2817 CPPUNIT_ASSERT_EQUAL(documentClassOffset(7), offsetof(struct _LibreOfficeKitDocumentClass, getPartName));
2818 CPPUNIT_ASSERT_EQUAL(documentClassOffset(8), offsetof(struct _LibreOfficeKitDocumentClass, setPartMode));
2819 CPPUNIT_ASSERT_EQUAL(documentClassOffset(9), offsetof(struct _LibreOfficeKitDocumentClass, paintTile));
2820 CPPUNIT_ASSERT_EQUAL(documentClassOffset(10), offsetof(struct _LibreOfficeKitDocumentClass, getTileMode));
2821 CPPUNIT_ASSERT_EQUAL(documentClassOffset(11), offsetof(struct _LibreOfficeKitDocumentClass, getDocumentSize));
2822 CPPUNIT_ASSERT_EQUAL(documentClassOffset(12), offsetof(struct _LibreOfficeKitDocumentClass, initializeForRendering));
2823 CPPUNIT_ASSERT_EQUAL(documentClassOffset(13), offsetof(struct _LibreOfficeKitDocumentClass, registerCallback));
2824 CPPUNIT_ASSERT_EQUAL(documentClassOffset(14), offsetof(struct _LibreOfficeKitDocumentClass, postKeyEvent));
2825 CPPUNIT_ASSERT_EQUAL(documentClassOffset(15), offsetof(struct _LibreOfficeKitDocumentClass, postMouseEvent));
2826 CPPUNIT_ASSERT_EQUAL(documentClassOffset(16), offsetof(struct _LibreOfficeKitDocumentClass, postUnoCommand));
2827 CPPUNIT_ASSERT_EQUAL(documentClassOffset(17), offsetof(struct _LibreOfficeKitDocumentClass, setTextSelection));
2828 CPPUNIT_ASSERT_EQUAL(documentClassOffset(18), offsetof(struct _LibreOfficeKitDocumentClass, getTextSelection));
2829 CPPUNIT_ASSERT_EQUAL(documentClassOffset(19), offsetof(struct _LibreOfficeKitDocumentClass, paste));
2830 CPPUNIT_ASSERT_EQUAL(documentClassOffset(20), offsetof(struct _LibreOfficeKitDocumentClass, setGraphicSelection));
2831 CPPUNIT_ASSERT_EQUAL(documentClassOffset(21), offsetof(struct _LibreOfficeKitDocumentClass, resetSelection));
2832 CPPUNIT_ASSERT_EQUAL(documentClassOffset(22), offsetof(struct _LibreOfficeKitDocumentClass, getCommandValues));
2833 CPPUNIT_ASSERT_EQUAL(documentClassOffset(23), offsetof(struct _LibreOfficeKitDocumentClass, setClientZoom));
2834 CPPUNIT_ASSERT_EQUAL(documentClassOffset(24), offsetof(struct _LibreOfficeKitDocumentClass, setClientVisibleArea));
2835 CPPUNIT_ASSERT_EQUAL(documentClassOffset(25), offsetof(struct _LibreOfficeKitDocumentClass, createView));
2836 CPPUNIT_ASSERT_EQUAL(documentClassOffset(26), offsetof(struct _LibreOfficeKitDocumentClass, destroyView));
2837 CPPUNIT_ASSERT_EQUAL(documentClassOffset(27), offsetof(struct _LibreOfficeKitDocumentClass, setView));
2838 CPPUNIT_ASSERT_EQUAL(documentClassOffset(28), offsetof(struct _LibreOfficeKitDocumentClass, getView));
2839 CPPUNIT_ASSERT_EQUAL(documentClassOffset(29), offsetof(struct _LibreOfficeKitDocumentClass, getViewsCount));
2840 CPPUNIT_ASSERT_EQUAL(documentClassOffset(30), offsetof(struct _LibreOfficeKitDocumentClass, renderFont));
2841 CPPUNIT_ASSERT_EQUAL(documentClassOffset(31), offsetof(struct _LibreOfficeKitDocumentClass, getPartHash));
2842 CPPUNIT_ASSERT_EQUAL(documentClassOffset(32), offsetof(struct _LibreOfficeKitDocumentClass, paintPartTile));
2843 CPPUNIT_ASSERT_EQUAL(documentClassOffset(33), offsetof(struct _LibreOfficeKitDocumentClass, getViewIds));
2844 CPPUNIT_ASSERT_EQUAL(documentClassOffset(34), offsetof(struct _LibreOfficeKitDocumentClass, setOutlineState));
2845 CPPUNIT_ASSERT_EQUAL(documentClassOffset(35), offsetof(struct _LibreOfficeKitDocumentClass, paintWindow));
2846 CPPUNIT_ASSERT_EQUAL(documentClassOffset(36), offsetof(struct _LibreOfficeKitDocumentClass, postWindow));
2847 CPPUNIT_ASSERT_EQUAL(documentClassOffset(37), offsetof(struct _LibreOfficeKitDocumentClass, postWindowKeyEvent));
2848 CPPUNIT_ASSERT_EQUAL(documentClassOffset(38), offsetof(struct _LibreOfficeKitDocumentClass, postWindowMouseEvent));
2849 CPPUNIT_ASSERT_EQUAL(documentClassOffset(39), offsetof(struct _LibreOfficeKitDocumentClass, setViewLanguage));
2850 CPPUNIT_ASSERT_EQUAL(documentClassOffset(40), offsetof(struct _LibreOfficeKitDocumentClass, postWindowExtTextInputEvent));
2851 CPPUNIT_ASSERT_EQUAL(documentClassOffset(41), offsetof(struct _LibreOfficeKitDocumentClass, getPartInfo));
2852 CPPUNIT_ASSERT_EQUAL(documentClassOffset(42), offsetof(struct _LibreOfficeKitDocumentClass, paintWindowDPI));
2853 CPPUNIT_ASSERT_EQUAL(documentClassOffset(43), offsetof(struct _LibreOfficeKitDocumentClass, insertCertificate));
2854 CPPUNIT_ASSERT_EQUAL(documentClassOffset(44), offsetof(struct _LibreOfficeKitDocumentClass, addCertificate));
2855 CPPUNIT_ASSERT_EQUAL(documentClassOffset(45), offsetof(struct _LibreOfficeKitDocumentClass, getSignatureState));
2856 CPPUNIT_ASSERT_EQUAL(documentClassOffset(46), offsetof(struct _LibreOfficeKitDocumentClass, renderShapeSelection));
2857 CPPUNIT_ASSERT_EQUAL(documentClassOffset(47), offsetof(struct _LibreOfficeKitDocumentClass, postWindowGestureEvent));
2858 CPPUNIT_ASSERT_EQUAL(documentClassOffset(48), offsetof(struct _LibreOfficeKitDocumentClass, createViewWithOptions));
2859 CPPUNIT_ASSERT_EQUAL(documentClassOffset(49), offsetof(struct _LibreOfficeKitDocumentClass, selectPart));
2860 CPPUNIT_ASSERT_EQUAL(documentClassOffset(50), offsetof(struct _LibreOfficeKitDocumentClass, moveSelectedParts));
2861 CPPUNIT_ASSERT_EQUAL(documentClassOffset(51), offsetof(struct _LibreOfficeKitDocumentClass, resizeWindow));
2862 CPPUNIT_ASSERT_EQUAL(documentClassOffset(52), offsetof(struct _LibreOfficeKitDocumentClass, getClipboard));
2863 CPPUNIT_ASSERT_EQUAL(documentClassOffset(53), offsetof(struct _LibreOfficeKitDocumentClass, setClipboard));
2864 CPPUNIT_ASSERT_EQUAL(documentClassOffset(54), offsetof(struct _LibreOfficeKitDocumentClass, getSelectionType));
2865 CPPUNIT_ASSERT_EQUAL(documentClassOffset(55), offsetof(struct _LibreOfficeKitDocumentClass, removeTextContext));
2866 CPPUNIT_ASSERT_EQUAL(documentClassOffset(56), offsetof(struct _LibreOfficeKitDocumentClass, sendDialogEvent));
2867 CPPUNIT_ASSERT_EQUAL(documentClassOffset(57), offsetof(struct _LibreOfficeKitDocumentClass, renderFontOrientation));
2868 CPPUNIT_ASSERT_EQUAL(documentClassOffset(58), offsetof(struct _LibreOfficeKitDocumentClass, paintWindowForView));
2869 CPPUNIT_ASSERT_EQUAL(documentClassOffset(59), offsetof(struct _LibreOfficeKitDocumentClass, completeFunction));
2871 // Extending is fine, update this, and add new assert for the offsetof the
2872 // new method
2873 CPPUNIT_ASSERT_EQUAL(documentClassOffset(60), sizeof(struct _LibreOfficeKitDocumentClass));
2876 CPPUNIT_TEST_SUITE_REGISTRATION(DesktopLOKTest);
2878 CPPUNIT_PLUGIN_IMPLEMENT();
2880 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */