summaryrefslogtreecommitdiffstats
path: root/sw/qa/extras/tiledrendering/tiledrendering.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /sw/qa/extras/tiledrendering/tiledrendering.cxx
parentInitial commit. (diff)
downloadlibreoffice-upstream.tar.xz
libreoffice-upstream.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/qa/extras/tiledrendering/tiledrendering.cxx')
-rw-r--r--sw/qa/extras/tiledrendering/tiledrendering.cxx2762
1 files changed, 2762 insertions, 0 deletions
diff --git a/sw/qa/extras/tiledrendering/tiledrendering.cxx b/sw/qa/extras/tiledrendering/tiledrendering.cxx
new file mode 100644
index 000000000..487def299
--- /dev/null
+++ b/sw/qa/extras/tiledrendering/tiledrendering.cxx
@@ -0,0 +1,2762 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <string>
+#include <boost/property_tree/json_parser.hpp>
+
+#include <com/sun/star/frame/DispatchResultState.hpp>
+#include <com/sun/star/frame/XDispatchResultListener.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+#include <swmodeltestbase.hxx>
+#include <test/helper/transferable.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <comphelper/dispatchcommand.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <comphelper/string.hxx>
+#include <comphelper/lok.hxx>
+#include <svx/svdpage.hxx>
+#include <svx/svdview.hxx>
+#include <vcl/virdev.hxx>
+#include <editeng/editview.hxx>
+#include <editeng/outliner.hxx>
+#include <svl/srchitem.hxx>
+#include <svl/slstitm.hxx>
+#include <svl/stritem.hxx>
+#include <drawdoc.hxx>
+#include <ndtxt.hxx>
+#include <wrtsh.hxx>
+#include <view.hxx>
+#include <UndoManager.hxx>
+#include <cmdid.h>
+#include <sfx2/viewsh.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/dispatch.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/lokhelper.hxx>
+#include <redline.hxx>
+#include <IDocumentDrawModelAccess.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <vcl/scheduler.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <svx/svxids.hrc>
+#include <flddat.hxx>
+#include <basesh.hxx>
+#include <vcl/ITiledRenderable.hxx>
+
+static char const DATA_DIRECTORY[] = "/sw/qa/extras/tiledrendering/data/";
+
+static std::ostream& operator<<(std::ostream& os, ViewShellId id)
+{
+ os << static_cast<sal_Int32>(id);
+ return os;
+}
+
+/// Testsuite for the SwXTextDocument methods implementing the vcl::ITiledRenderable interface.
+class SwTiledRenderingTest : public SwModelTestBase
+{
+public:
+ SwTiledRenderingTest();
+ virtual void setUp() override;
+ virtual void tearDown() override;
+ void testRegisterCallback();
+ void testPostKeyEvent();
+ void testPostMouseEvent();
+ void testSetTextSelection();
+ void testGetTextSelection();
+ void testSetGraphicSelection();
+ void testResetSelection();
+ void testInsertShape();
+ void testSearch();
+ void testSearchViewArea();
+ void testSearchTextFrame();
+ void testSearchTextFrameWrapAround();
+ void testDocumentSizeChanged();
+ void testSearchAll();
+ void testSearchAllNotifications();
+ void testPageDownInvalidation();
+ void testPartHash();
+ void testViewCursors();
+ void testShapeViewCursors();
+ void testMissingInvalidation();
+ void testViewCursorVisibility();
+ void testViewCursorCleanup();
+ void testViewLock();
+ void testTextEditViewInvalidations();
+ void testUndoInvalidations();
+ void testUndoLimiting();
+ void testUndoShapeLimiting();
+ void testUndoDispatch();
+ void testUndoRepairDispatch();
+ void testShapeTextUndoShells();
+ void testShapeTextUndoGroupShells();
+ void testTrackChanges();
+ void testTrackChangesCallback();
+ void testRedlineUpdateCallback();
+ void testSetViewGraphicSelection();
+ void testCreateViewGraphicSelection();
+ void testCreateViewTextSelection();
+ void testRedlineColors();
+ void testCommentEndTextEdit();
+ void testCommentInsert();
+ void testCursorPosition();
+ void testPaintCallbacks();
+ void testUndoRepairResult();
+ void testRedoRepairResult();
+ void testDisableUndoRepair();
+ void testAllTrackedChanges();
+ void testDocumentRepair();
+ void testPageHeader();
+ void testPageFooter();
+ void testTdf115088();
+ void testRedlineField();
+ void testIMESupport();
+ void testSplitNodeRedlineCallback();
+ void testDeleteNodeRedlineCallback();
+ void testVisCursorInvalidation();
+ void testDeselectCustomShape();
+ void testSemiTransparent();
+ void testHighlightNumbering();
+ void testClipText();
+ void testAnchorTypes();
+ void testLanguageStatus();
+ void testRedlineNotificationDuringSave();
+ void testHyperlink();
+ void testFieldmark();
+ void testDropDownFormFieldButton();
+ void testDropDownFormFieldButtonEditing();
+ void testDropDownFormFieldButtonNoSelection();
+ void testDropDownFormFieldButtonNoItem();
+
+ CPPUNIT_TEST_SUITE(SwTiledRenderingTest);
+ CPPUNIT_TEST(testRegisterCallback);
+ CPPUNIT_TEST(testPostKeyEvent);
+ CPPUNIT_TEST(testPostMouseEvent);
+ CPPUNIT_TEST(testSetTextSelection);
+ CPPUNIT_TEST(testGetTextSelection);
+ CPPUNIT_TEST(testSetGraphicSelection);
+ CPPUNIT_TEST(testResetSelection);
+ CPPUNIT_TEST(testInsertShape);
+ CPPUNIT_TEST(testSearch);
+ CPPUNIT_TEST(testSearchViewArea);
+ CPPUNIT_TEST(testSearchTextFrame);
+ CPPUNIT_TEST(testSearchTextFrameWrapAround);
+ CPPUNIT_TEST(testDocumentSizeChanged);
+ CPPUNIT_TEST(testSearchAll);
+ CPPUNIT_TEST(testSearchAllNotifications);
+ CPPUNIT_TEST(testPageDownInvalidation);
+ CPPUNIT_TEST(testPartHash);
+ CPPUNIT_TEST(testViewCursors);
+ CPPUNIT_TEST(testShapeViewCursors);
+ CPPUNIT_TEST(testMissingInvalidation);
+ CPPUNIT_TEST(testViewCursorVisibility);
+ CPPUNIT_TEST(testViewCursorCleanup);
+ CPPUNIT_TEST(testViewLock);
+ CPPUNIT_TEST(testTextEditViewInvalidations);
+ CPPUNIT_TEST(testUndoInvalidations);
+ CPPUNIT_TEST(testUndoLimiting);
+ CPPUNIT_TEST(testUndoShapeLimiting);
+ CPPUNIT_TEST(testUndoDispatch);
+ CPPUNIT_TEST(testUndoRepairDispatch);
+ CPPUNIT_TEST(testShapeTextUndoShells);
+ CPPUNIT_TEST(testShapeTextUndoGroupShells);
+ CPPUNIT_TEST(testTrackChanges);
+ CPPUNIT_TEST(testTrackChangesCallback);
+ CPPUNIT_TEST(testRedlineUpdateCallback);
+ CPPUNIT_TEST(testSetViewGraphicSelection);
+ CPPUNIT_TEST(testCreateViewGraphicSelection);
+ CPPUNIT_TEST(testCreateViewTextSelection);
+ CPPUNIT_TEST(testRedlineColors);
+ CPPUNIT_TEST(testCommentEndTextEdit);
+ CPPUNIT_TEST(testCommentInsert);
+ CPPUNIT_TEST(testCursorPosition);
+ CPPUNIT_TEST(testPaintCallbacks);
+ CPPUNIT_TEST(testUndoRepairResult);
+ CPPUNIT_TEST(testRedoRepairResult);
+ CPPUNIT_TEST(testDisableUndoRepair);
+ CPPUNIT_TEST(testAllTrackedChanges);
+ CPPUNIT_TEST(testDocumentRepair);
+ CPPUNIT_TEST(testPageHeader);
+ CPPUNIT_TEST(testPageFooter);
+ CPPUNIT_TEST(testTdf115088);
+ CPPUNIT_TEST(testRedlineField);
+ CPPUNIT_TEST(testIMESupport);
+ CPPUNIT_TEST(testSplitNodeRedlineCallback);
+ CPPUNIT_TEST(testDeleteNodeRedlineCallback);
+ CPPUNIT_TEST(testVisCursorInvalidation);
+ CPPUNIT_TEST(testDeselectCustomShape);
+ CPPUNIT_TEST(testSemiTransparent);
+ CPPUNIT_TEST(testHighlightNumbering);
+ CPPUNIT_TEST(testClipText);
+ CPPUNIT_TEST(testAnchorTypes);
+ CPPUNIT_TEST(testLanguageStatus);
+ CPPUNIT_TEST(testRedlineNotificationDuringSave);
+ CPPUNIT_TEST(testHyperlink);
+ CPPUNIT_TEST(testFieldmark);
+ CPPUNIT_TEST(testDropDownFormFieldButton);
+ CPPUNIT_TEST(testDropDownFormFieldButtonEditing);
+ CPPUNIT_TEST(testDropDownFormFieldButtonNoSelection);
+ CPPUNIT_TEST(testDropDownFormFieldButtonNoItem);
+ CPPUNIT_TEST_SUITE_END();
+
+private:
+ SwXTextDocument* createDoc(const char* pName = nullptr);
+ static void callback(int nType, const char* pPayload, void* pData);
+ void callbackImpl(int nType, const char* pPayload);
+ tools::Rectangle m_aInvalidation;
+ Size m_aDocumentSize;
+ OString m_aTextSelection;
+ bool m_bFound;
+ std::vector<OString> m_aSearchResultSelection;
+ std::vector<int> m_aSearchResultPart;
+ int m_nSelectionBeforeSearchResult;
+ int m_nSelectionAfterSearchResult;
+ int m_nInvalidations;
+ int m_nRedlineTableSizeChanged;
+ int m_nRedlineTableEntryModified;
+ int m_nTrackedChangeIndex;
+ OString m_sHyperlinkText;
+ OString m_sHyperlinkLink;
+ OString m_aFormFieldButton;
+};
+
+SwTiledRenderingTest::SwTiledRenderingTest()
+ : m_bFound(true),
+ m_nSelectionBeforeSearchResult(0),
+ m_nSelectionAfterSearchResult(0),
+ m_nInvalidations(0),
+ m_nRedlineTableSizeChanged(0),
+ m_nRedlineTableEntryModified(0),
+ m_nTrackedChangeIndex(-1)
+{
+}
+
+void SwTiledRenderingTest::setUp()
+{
+ SwModelTestBase::setUp();
+
+ comphelper::LibreOfficeKit::setActive(true);
+}
+
+void SwTiledRenderingTest::tearDown()
+{
+ if (mxComponent.is())
+ {
+ SwXTextDocument* pTextDocument = dynamic_cast<SwXTextDocument*>(mxComponent.get());
+ if (pTextDocument)
+ {
+ SwWrtShell* pWrtShell = pTextDocument->GetDocShell()->GetWrtShell();
+ if (pWrtShell)
+ {
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+ }
+ }
+ mxComponent->dispose();
+ mxComponent.clear();
+ }
+ comphelper::LibreOfficeKit::setActive(false);
+
+ test::BootstrapFixture::tearDown();
+}
+
+SwXTextDocument* SwTiledRenderingTest::createDoc(const char* pName)
+{
+ if (!pName)
+ loadURL("private:factory/swriter", nullptr);
+ else
+ load(DATA_DIRECTORY, pName);
+
+ SwXTextDocument* pTextDocument = dynamic_cast<SwXTextDocument*>(mxComponent.get());
+ CPPUNIT_ASSERT(pTextDocument);
+ pTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ return pTextDocument;
+}
+
+void SwTiledRenderingTest::callback(int nType, const char* pPayload, void* pData)
+{
+ static_cast<SwTiledRenderingTest*>(pData)->callbackImpl(nType, pPayload);
+}
+
+void SwTiledRenderingTest::callbackImpl(int nType, const char* pPayload)
+{
+ OString aPayload(pPayload);
+ switch (nType)
+ {
+ case LOK_CALLBACK_INVALIDATE_TILES:
+ {
+ if (m_aInvalidation.IsEmpty())
+ {
+ uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::createFromAscii(pPayload));
+ if (OString("EMPTY") == pPayload)
+ return;
+ CPPUNIT_ASSERT(aSeq.getLength() == 4 || aSeq.getLength() == 5);
+ m_aInvalidation.setX(aSeq[0].toInt32());
+ m_aInvalidation.setY(aSeq[1].toInt32());
+ m_aInvalidation.setWidth(aSeq[2].toInt32());
+ m_aInvalidation.setHeight(aSeq[3].toInt32());
+ }
+ ++m_nInvalidations;
+ }
+ break;
+ case LOK_CALLBACK_DOCUMENT_SIZE_CHANGED:
+ {
+ uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::createFromAscii(pPayload));
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), aSeq.getLength());
+ m_aDocumentSize.setWidth(aSeq[0].toInt32());
+ m_aDocumentSize.setHeight(aSeq[1].toInt32());
+ }
+ break;
+ case LOK_CALLBACK_TEXT_SELECTION:
+ {
+ m_aTextSelection = pPayload;
+ if (m_aSearchResultSelection.empty())
+ ++m_nSelectionBeforeSearchResult;
+ else
+ ++m_nSelectionAfterSearchResult;
+ }
+ break;
+ case LOK_CALLBACK_SEARCH_NOT_FOUND:
+ {
+ m_bFound = false;
+ }
+ break;
+ case LOK_CALLBACK_SEARCH_RESULT_SELECTION:
+ {
+ m_aSearchResultSelection.clear();
+ boost::property_tree::ptree aTree;
+ std::stringstream aStream(pPayload);
+ boost::property_tree::read_json(aStream, aTree);
+ for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("searchResultSelection"))
+ {
+ m_aSearchResultSelection.emplace_back(rValue.second.get<std::string>("rectangles").c_str());
+ m_aSearchResultPart.push_back(std::atoi(rValue.second.get<std::string>("part").c_str()));
+ }
+ }
+ break;
+ case LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED:
+ {
+ ++m_nRedlineTableSizeChanged;
+ }
+ break;
+ case LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED:
+ {
+ ++m_nRedlineTableEntryModified;
+ }
+ break;
+ case LOK_CALLBACK_STATE_CHANGED:
+ {
+ OString aTrackedChangeIndexPrefix(".uno:TrackedChangeIndex=");
+ if (aPayload.startsWith(aTrackedChangeIndexPrefix))
+ {
+ OString sIndex = aPayload.copy(aTrackedChangeIndexPrefix.getLength());
+ if (sIndex.isEmpty())
+ m_nTrackedChangeIndex = -1;
+ else
+ m_nTrackedChangeIndex = sIndex.toInt32();
+ }
+ }
+ break;
+ case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
+ {
+ if (comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation())
+ {
+ boost::property_tree::ptree aTree;
+ std::stringstream aStream(pPayload);
+ boost::property_tree::read_json(aStream, aTree);
+ boost::property_tree::ptree &aChild = aTree.get_child("hyperlink");
+ m_sHyperlinkText = aChild.get("text", "").c_str();
+ m_sHyperlinkLink = aChild.get("link", "").c_str();
+ }
+ }
+ break;
+ case LOK_CALLBACK_FORM_FIELD_BUTTON:
+ {
+ m_aFormFieldButton = OString(pPayload);
+ }
+ break;
+ }
+}
+
+void SwTiledRenderingTest::testRegisterCallback()
+{
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+ // Insert a character at the beginning of the document.
+ pWrtShell->Insert("x");
+
+ // Check that the top left 256x256px tile would be invalidated.
+ CPPUNIT_ASSERT(!m_aInvalidation.IsEmpty());
+ tools::Rectangle aTopLeft(0, 0, 256*15, 256*15); // 1 px = 15 twips, assuming 96 DPI.
+ CPPUNIT_ASSERT(m_aInvalidation.IsOver(aTopLeft));
+}
+
+void SwTiledRenderingTest::testPostKeyEvent()
+{
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false);
+ SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
+ // Did we manage to go after the first character?
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), pShellCursor->GetPoint()->nContent.GetIndex());
+
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ Scheduler::ProcessEventsToIdle();
+ // Did we manage to insert the character after the first one?
+ CPPUNIT_ASSERT_EQUAL(OUString("Axaa bbb."), pShellCursor->GetPoint()->nNode.GetNode().GetTextNode()->GetText());
+}
+
+void SwTiledRenderingTest::testPostMouseEvent()
+{
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false);
+ SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
+ // Did we manage to go after the first character?
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), pShellCursor->GetPoint()->nContent.GetIndex());
+
+ Point aStart = pShellCursor->GetSttPos();
+ aStart.setX(aStart.getX() - 1000);
+ pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aStart.getX(), aStart.getY(), 1, MOUSE_LEFT, 0);
+ pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, aStart.getX(), aStart.getY(), 1, MOUSE_LEFT, 0);
+ Scheduler::ProcessEventsToIdle();
+ // The new cursor position must be before the first word.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0), pShellCursor->GetPoint()->nContent.GetIndex());
+}
+
+void SwTiledRenderingTest::testSetTextSelection()
+{
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ // Move the cursor into the second word.
+ pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 5, /*bBasicCall=*/false);
+ // Create a selection on the word.
+ pWrtShell->SelWrd();
+ SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
+ // Did we indeed manage to select the second word?
+ CPPUNIT_ASSERT_EQUAL(OUString("bbb"), pShellCursor->GetText());
+
+ // Now use setTextSelection() to move the start of the selection 1000 twips left.
+ Point aStart = pShellCursor->GetSttPos();
+ aStart.setX(aStart.getX() - 1000);
+ pXTextDocument->setTextSelection(LOK_SETTEXTSELECTION_START, aStart.getX(), aStart.getY());
+ // The new selection must include the first word, too -- but not the ending dot.
+ CPPUNIT_ASSERT_EQUAL(OUString("Aaa bbb"), pShellCursor->GetText());
+
+ // Next: test that LOK_SETTEXTSELECTION_RESET + LOK_SETTEXTSELECTION_END can be used to create a selection.
+ pXTextDocument->setTextSelection(LOK_SETTEXTSELECTION_RESET, aStart.getX(), aStart.getY());
+ pXTextDocument->setTextSelection(LOK_SETTEXTSELECTION_END, aStart.getX() + 1000, aStart.getY());
+ CPPUNIT_ASSERT_EQUAL(OUString("Aaa b"), pShellCursor->GetText());
+}
+
+void SwTiledRenderingTest::testGetTextSelection()
+{
+ SwXTextDocument* pXTextDocument = createDoc("shape-with-text.fodt");
+ // No crash, just empty output for unexpected mime type.
+ CPPUNIT_ASSERT_EQUAL(OString(), apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "foo/bar"));
+
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ // Move the cursor into the first word.
+ pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 2, /*bBasicCall=*/false);
+ // Create a selection by on the word.
+ pWrtShell->SelWrd();
+
+ // Make sure that we selected text from the body text.
+ CPPUNIT_ASSERT_EQUAL(OString("Hello"), apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/plain;charset=utf-8"));
+
+ // Make sure we produce something for HTML.
+ CPPUNIT_ASSERT(!apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/html").isEmpty());
+
+ // Now select some shape text and check again.
+ SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
+ SdrObject* pObject = pPage->GetObj(0);
+ SdrView* pView = pWrtShell->GetDrawView();
+ pView->SdrBeginTextEdit(pObject);
+ CPPUNIT_ASSERT(pView->GetTextEditObject());
+ EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView();
+ ESelection aWordSelection(0, 0, 0, 5);
+ rEditView.SetSelection(aWordSelection);
+ CPPUNIT_ASSERT_EQUAL(OString("Shape"), apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/plain;charset=utf-8"));
+}
+
+void SwTiledRenderingTest::testSetGraphicSelection()
+{
+ SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
+ SdrObject* pObject = pPage->GetObj(0);
+ pWrtShell->SelectObj(Point(), 0, pObject);
+ SdrHdlList handleList(nullptr);
+ pObject->AddToHdlList(handleList);
+ // Make sure the rectangle has 8 handles: at each corner and at the center of each edge.
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(8), handleList.GetHdlCount());
+ // Take the bottom center one.
+ SdrHdl* pHdl = handleList.GetHdl(6);
+ CPPUNIT_ASSERT_EQUAL(int(SdrHdlKind::Lower), static_cast<int>(pHdl->GetKind()));
+ tools::Rectangle aShapeBefore = pObject->GetSnapRect();
+ // Resize.
+ pXTextDocument->setGraphicSelection(LOK_SETGRAPHICSELECTION_START, pHdl->GetPos().getX(), pHdl->GetPos().getY());
+ pXTextDocument->setGraphicSelection(LOK_SETGRAPHICSELECTION_END, pHdl->GetPos().getX(), pHdl->GetPos().getY() + 1000);
+ tools::Rectangle aShapeAfter = pObject->GetSnapRect();
+ // Check that a resize happened, but aspect ratio is not kept.
+ CPPUNIT_ASSERT_EQUAL(aShapeBefore.getWidth(), aShapeAfter.getWidth());
+ CPPUNIT_ASSERT_EQUAL(aShapeBefore.getHeight() + 1000, aShapeAfter.getHeight());
+}
+
+void SwTiledRenderingTest::testResetSelection()
+{
+ SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ // Select one character.
+ pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/true, 1, /*bBasicCall=*/false);
+ SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
+ // We have a text selection.
+ CPPUNIT_ASSERT(pShellCursor->HasMark());
+
+ pXTextDocument->resetSelection();
+ // We no longer have a text selection.
+ CPPUNIT_ASSERT(!pShellCursor->HasMark());
+
+ SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
+ SdrObject* pObject = pPage->GetObj(0);
+ Point aPoint = pObject->GetSnapRect().Center();
+ // Select the shape.
+ pWrtShell->EnterSelFrameMode(&aPoint);
+ // We have a graphic selection.
+ CPPUNIT_ASSERT(pWrtShell->IsSelFrameMode());
+
+ pXTextDocument->resetSelection();
+ // We no longer have a graphic selection.
+ CPPUNIT_ASSERT(!pWrtShell->IsSelFrameMode());
+}
+
+void SwTiledRenderingTest::testInsertShape()
+{
+ SwXTextDocument* pXTextDocument = createDoc("2-pages.odt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+
+ pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000));
+ comphelper::dispatchCommand(".uno:BasicShapes.circle", uno::Sequence<beans::PropertyValue>());
+
+ // check that the shape was inserted in the visible area, not outside
+ IDocumentDrawModelAccess &rDrawModelAccess = pWrtShell->GetDoc()->getIDocumentDrawModelAccess();
+ SdrPage* pPage = rDrawModelAccess.GetDrawModel()->GetPage(0);
+ SdrObject* pObject = pPage->GetObj(0);
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(3302, 302, 6698, 3698), pObject->GetSnapRect());
+
+ // check that it is in the foreground layer
+ CPPUNIT_ASSERT_EQUAL(rDrawModelAccess.GetHeavenId().get(), pObject->GetLayer().get());
+}
+
+static void lcl_search(bool bBackward)
+{
+ uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
+ {
+ {"SearchItem.SearchString", uno::makeAny(OUString("shape"))},
+ {"SearchItem.Backward", uno::makeAny(bBackward)}
+ }));
+ comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues);
+}
+
+void SwTiledRenderingTest::testSearch()
+{
+ SwXTextDocument* pXTextDocument = createDoc("search.odt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+ std::size_t nNode = pWrtShell->getShellCursor(false)->Start()->nNode.GetNode().GetIndex();
+
+ // First hit, in the second paragraph, before the shape.
+ lcl_search(false);
+ CPPUNIT_ASSERT(!pWrtShell->GetDrawView()->GetTextEditObject());
+ std::size_t nActual = pWrtShell->getShellCursor(false)->Start()->nNode.GetNode().GetIndex();
+ CPPUNIT_ASSERT_EQUAL(nNode + 1, nActual);
+ /// Make sure we get search result selection for normal find as well, not only find all.
+ CPPUNIT_ASSERT(!m_aSearchResultSelection.empty());
+
+ // Next hit, in the shape.
+ lcl_search(false);
+ CPPUNIT_ASSERT(pWrtShell->GetDrawView()->GetTextEditObject());
+
+ // Next hit, in the shape, still.
+ lcl_search(false);
+ CPPUNIT_ASSERT(pWrtShell->GetDrawView()->GetTextEditObject());
+
+ // Last hit, in the last paragraph, after the shape.
+ lcl_search(false);
+ CPPUNIT_ASSERT(!pWrtShell->GetDrawView()->GetTextEditObject());
+ nActual = pWrtShell->getShellCursor(false)->Start()->nNode.GetNode().GetIndex();
+ CPPUNIT_ASSERT_EQUAL(nNode + 7, nActual);
+
+ // Now change direction and make sure that the first 2 hits are in the shape, but not the 3rd one.
+ lcl_search(true);
+ CPPUNIT_ASSERT(pWrtShell->GetDrawView()->GetTextEditObject());
+ lcl_search(true);
+ CPPUNIT_ASSERT(pWrtShell->GetDrawView()->GetTextEditObject());
+ lcl_search(true);
+ CPPUNIT_ASSERT(!pWrtShell->GetDrawView()->GetTextEditObject());
+ nActual = pWrtShell->getShellCursor(false)->Start()->nNode.GetNode().GetIndex();
+ CPPUNIT_ASSERT_EQUAL(nNode + 1, nActual);
+}
+
+void SwTiledRenderingTest::testSearchViewArea()
+{
+ SwXTextDocument* pXTextDocument = createDoc("search.odt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ // Go to the second page, 1-based.
+ pWrtShell->GotoPage(2, false);
+ SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
+ // Get the ~top left corner of the second page.
+ Point aPoint = pShellCursor->GetSttPos();
+
+ // Go back to the first page, search while the cursor is there, but the
+ // visible area is the second page.
+ pWrtShell->GotoPage(1, false);
+ uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
+ {
+ {"SearchItem.SearchString", uno::makeAny(OUString("Heading"))},
+ {"SearchItem.Backward", uno::makeAny(false)},
+ {"SearchItem.SearchStartPointX", uno::makeAny(static_cast<sal_Int32>(aPoint.getX()))},
+ {"SearchItem.SearchStartPointY", uno::makeAny(static_cast<sal_Int32>(aPoint.getY()))}
+ }));
+ comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues);
+ // This was just "Heading", i.e. SwView::SearchAndWrap() did not search from only the top of the second page.
+ CPPUNIT_ASSERT_EQUAL(OUString("Heading on second page"), pShellCursor->GetPoint()->nNode.GetNode().GetTextNode()->GetText());
+}
+
+void SwTiledRenderingTest::testSearchTextFrame()
+{
+ SwXTextDocument* pXTextDocument = createDoc("search.odt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+ uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
+ {
+ {"SearchItem.SearchString", uno::makeAny(OUString("TextFrame"))},
+ {"SearchItem.Backward", uno::makeAny(false)},
+ }));
+ comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues);
+ // This was empty: nothing was highlighted after searching for 'TextFrame'.
+ CPPUNIT_ASSERT(!m_aTextSelection.isEmpty());
+}
+
+void SwTiledRenderingTest::testSearchTextFrameWrapAround()
+{
+ SwXTextDocument* pXTextDocument = createDoc("search.odt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+ uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
+ {
+ {"SearchItem.SearchString", uno::makeAny(OUString("TextFrame"))},
+ {"SearchItem.Backward", uno::makeAny(false)},
+ }));
+ comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues);
+ CPPUNIT_ASSERT(m_bFound);
+ comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues);
+ // This failed, i.e. the second time 'not found' was reported, instead of wrapping around.
+ CPPUNIT_ASSERT(m_bFound);
+}
+
+void SwTiledRenderingTest::testDocumentSizeChanged()
+{
+ // Get the current document size.
+ SwXTextDocument* pXTextDocument = createDoc("2-pages.odt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+ Size aSize = pXTextDocument->getDocumentSize();
+
+ // Delete the second page and see how the size changes.
+ pWrtShell->Down(false);
+ pWrtShell->DelLeft();
+ // Document width should not change, this was 0.
+ CPPUNIT_ASSERT_EQUAL(aSize.getWidth(), m_aDocumentSize.getWidth());
+ // Document height should be smaller now.
+ CPPUNIT_ASSERT(aSize.getHeight() > m_aDocumentSize.getHeight());
+}
+
+void SwTiledRenderingTest::testSearchAll()
+{
+ SwXTextDocument* pXTextDocument = createDoc("search.odt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+ uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
+ {
+ {"SearchItem.SearchString", uno::makeAny(OUString("shape"))},
+ {"SearchItem.Backward", uno::makeAny(false)},
+ {"SearchItem.Command", uno::makeAny(static_cast<sal_uInt16>(SvxSearchCmd::FIND_ALL))},
+ }));
+ comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues);
+ // This was 0; should be 2 results in the body text.
+ CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(2), m_aSearchResultSelection.size());
+ // Writer documents are always a single part.
+ CPPUNIT_ASSERT_EQUAL(0, m_aSearchResultPart[0]);
+}
+
+void SwTiledRenderingTest::testSearchAllNotifications()
+{
+ SwXTextDocument* pXTextDocument = createDoc("search.odt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+ // Reset notification counter before search.
+ m_nSelectionBeforeSearchResult = 0;
+ uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
+ {
+ {"SearchItem.SearchString", uno::makeAny(OUString("shape"))},
+ {"SearchItem.Backward", uno::makeAny(false)},
+ {"SearchItem.Command", uno::makeAny(static_cast<sal_uInt16>(SvxSearchCmd::FIND_ALL))},
+ }));
+ comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues);
+ Scheduler::ProcessEventsToIdle();
+
+ // This was 5, make sure that we get no notifications about selection changes during search.
+ CPPUNIT_ASSERT_EQUAL(0, m_nSelectionBeforeSearchResult);
+ // But we do get the selection afterwards.
+ CPPUNIT_ASSERT(m_nSelectionAfterSearchResult > 0);
+}
+
+void SwTiledRenderingTest::testPageDownInvalidation()
+{
+ SwXTextDocument* pXTextDocument = createDoc("pagedown-invalidation.odt");
+ uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
+ {
+ {".uno:HideWhitespace", uno::makeAny(true)},
+ }));
+ pXTextDocument->initializeForTiledRendering(aPropertyValues);
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+ comphelper::dispatchCommand(".uno:PageDown", uno::Sequence<beans::PropertyValue>());
+
+ // This was 2.
+ CPPUNIT_ASSERT_EQUAL(0, m_nInvalidations);
+}
+
+void SwTiledRenderingTest::testPartHash()
+{
+ SwXTextDocument* pXTextDocument = createDoc("pagedown-invalidation.odt");
+ int nParts = pXTextDocument->getParts();
+ for (int it = 0; it < nParts; it++)
+ {
+ CPPUNIT_ASSERT(!pXTextDocument->getPartHash(it).isEmpty());
+ }
+}
+
+namespace {
+
+/// A view callback tracks callbacks invoked on one specific view.
+class ViewCallback
+{
+ SfxViewShell* mpViewShell;
+ int mnView;
+public:
+ bool m_bOwnCursorInvalidated;
+ int m_nOwnCursorInvalidatedBy;
+ bool m_bOwnCursorAtOrigin;
+ tools::Rectangle m_aOwnCursor;
+ bool m_bViewCursorInvalidated;
+ tools::Rectangle m_aViewCursor;
+ bool m_bOwnSelectionSet;
+ bool m_bViewSelectionSet;
+ OString m_aViewSelection;
+ bool m_bTilesInvalidated;
+ bool m_bViewCursorVisible;
+ bool m_bGraphicViewSelection;
+ bool m_bGraphicSelection;
+ bool m_bViewLock;
+ /// Set if any callback was invoked.
+ bool m_bCalled;
+ /// Redline table size changed payload
+ boost::property_tree::ptree m_aRedlineTableChanged;
+ /// Redline table modified payload
+ boost::property_tree::ptree m_aRedlineTableModified;
+ /// Post-it / annotation payload.
+ boost::property_tree::ptree m_aComment;
+
+ ViewCallback(SfxViewShell* pViewShell, std::function<void(ViewCallback&)> const & rBeforeInstallFunc = {})
+ : m_bOwnCursorInvalidated(false),
+ m_nOwnCursorInvalidatedBy(-1),
+ m_bOwnCursorAtOrigin(false),
+ m_bViewCursorInvalidated(false),
+ m_bOwnSelectionSet(false),
+ m_bViewSelectionSet(false),
+ m_bTilesInvalidated(false),
+ m_bViewCursorVisible(false),
+ m_bGraphicViewSelection(false),
+ m_bGraphicSelection(false),
+ m_bViewLock(false),
+ m_bCalled(false)
+ {
+ // Because one call-site wants to set the bool fields up before the callback is installed
+ if (rBeforeInstallFunc)
+ rBeforeInstallFunc(*this);
+
+ mpViewShell = pViewShell;
+ mpViewShell->registerLibreOfficeKitViewCallback(&ViewCallback::callback, this);
+ mnView = SfxLokHelper::getView();
+ }
+
+ ~ViewCallback()
+ {
+ SfxLokHelper::setView(mnView);
+ mpViewShell->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+ }
+
+ static void callback(int nType, const char* pPayload, void* pData)
+ {
+ static_cast<ViewCallback*>(pData)->callbackImpl(nType, pPayload);
+ }
+
+ void callbackImpl(int nType, const char* pPayload)
+ {
+ OString aPayload(pPayload);
+ m_bCalled = true;
+ switch (nType)
+ {
+ case LOK_CALLBACK_INVALIDATE_TILES:
+ {
+ m_bTilesInvalidated = true;
+ }
+ break;
+ case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
+ {
+ m_bOwnCursorInvalidated = true;
+
+ OString sRect;
+ if(comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation())
+ {
+ std::stringstream aStream(pPayload);
+ boost::property_tree::ptree aTree;
+ boost::property_tree::read_json(aStream, aTree);
+ sRect = aTree.get_child("rectangle").get_value<std::string>().c_str();
+ m_nOwnCursorInvalidatedBy = aTree.get_child("viewId").get_value<int>();
+ }
+ else
+ sRect = aPayload;
+ uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::fromUtf8(sRect));
+ if (OString("EMPTY") == pPayload)
+ return;
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(4), aSeq.getLength());
+ m_aOwnCursor.setX(aSeq[0].toInt32());
+ m_aOwnCursor.setY(aSeq[1].toInt32());
+ m_aOwnCursor.setWidth(aSeq[2].toInt32());
+ m_aOwnCursor.setHeight(aSeq[3].toInt32());
+ if (m_aOwnCursor.getX() == 0 && m_aOwnCursor.getY() == 0)
+ m_bOwnCursorAtOrigin = true;
+ }
+ break;
+ case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
+ {
+ m_bViewCursorInvalidated = true;
+ std::stringstream aStream(pPayload);
+ boost::property_tree::ptree aTree;
+ boost::property_tree::read_json(aStream, aTree);
+ OString aRect = aTree.get_child("rectangle").get_value<std::string>().c_str();
+
+ uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::fromUtf8(aRect));
+ if (OString("EMPTY") == pPayload)
+ return;
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(4), aSeq.getLength());
+ m_aViewCursor.setX(aSeq[0].toInt32());
+ m_aViewCursor.setY(aSeq[1].toInt32());
+ m_aViewCursor.setWidth(aSeq[2].toInt32());
+ m_aViewCursor.setHeight(aSeq[3].toInt32());
+ }
+ break;
+ case LOK_CALLBACK_TEXT_SELECTION:
+ {
+ m_bOwnSelectionSet = true;
+ }
+ break;
+ case LOK_CALLBACK_TEXT_VIEW_SELECTION:
+ {
+ m_bViewSelectionSet = true;
+ m_aViewSelection = aPayload;
+ }
+ break;
+ case LOK_CALLBACK_VIEW_CURSOR_VISIBLE:
+ {
+ std::stringstream aStream(pPayload);
+ boost::property_tree::ptree aTree;
+ boost::property_tree::read_json(aStream, aTree);
+ m_bViewCursorVisible = aTree.get_child("visible").get_value<std::string>() == "true";
+ }
+ break;
+ case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
+ {
+ std::stringstream aStream(pPayload);
+ boost::property_tree::ptree aTree;
+ boost::property_tree::read_json(aStream, aTree);
+ m_bGraphicViewSelection = aTree.get_child("selection").get_value<std::string>() != "EMPTY";
+ }
+ break;
+ case LOK_CALLBACK_GRAPHIC_SELECTION:
+ {
+ m_bGraphicSelection = aPayload != "EMPTY";
+ }
+ break;
+ case LOK_CALLBACK_VIEW_LOCK:
+ {
+ std::stringstream aStream(pPayload);
+ boost::property_tree::ptree aTree;
+ boost::property_tree::read_json(aStream, aTree);
+ m_bViewLock = aTree.get_child("rectangle").get_value<std::string>() != "EMPTY";
+ }
+ break;
+ case LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED:
+ {
+ m_aRedlineTableChanged.clear();
+ std::stringstream aStream(pPayload);
+ boost::property_tree::read_json(aStream, m_aRedlineTableChanged);
+ m_aRedlineTableChanged = m_aRedlineTableChanged.get_child("redline");
+ }
+ break;
+ case LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED:
+ {
+ m_aRedlineTableModified.clear();
+ std::stringstream aStream(pPayload);
+ boost::property_tree::read_json(aStream, m_aRedlineTableModified);
+ m_aRedlineTableModified = m_aRedlineTableModified.get_child("redline");
+ }
+ break;
+ case LOK_CALLBACK_COMMENT:
+ {
+ m_aComment.clear();
+ std::stringstream aStream(pPayload);
+ boost::property_tree::read_json(aStream, m_aComment);
+ m_aComment = m_aComment.get_child("comment");
+ }
+ break;
+ }
+ }
+};
+
+class TestResultListener : public cppu::WeakImplHelper<css::frame::XDispatchResultListener>
+{
+public:
+ sal_uInt32 m_nDocRepair;
+
+ TestResultListener() : m_nDocRepair(0)
+ {
+ }
+
+ virtual void SAL_CALL dispatchFinished(const css::frame::DispatchResultEvent& rEvent) override
+ {
+ if (rEvent.State == frame::DispatchResultState::SUCCESS)
+ {
+ rEvent.Result >>= m_nDocRepair;
+ }
+ }
+
+ virtual void SAL_CALL disposing(const css::lang::EventObject&) override
+ {
+ }
+};
+
+}
+
+void SwTiledRenderingTest::testMissingInvalidation()
+{
+ // Create two views.
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ ViewCallback aView1(SfxViewShell::Current());
+ int nView1 = SfxLokHelper::getView();
+ SfxLokHelper::createView();
+ ViewCallback aView2(SfxViewShell::Current());
+ int nView2 = SfxLokHelper::getView();
+
+ // First view: put the cursor into the first word.
+ SfxLokHelper::setView(nView1);
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false);
+
+ // Second view: select the first word.
+ SfxLokHelper::setView(nView2);
+ CPPUNIT_ASSERT(pXTextDocument->GetDocShell()->GetWrtShell() != pWrtShell);
+ pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false);
+ pWrtShell->SelWrd();
+
+ // Now delete the selected word and make sure both views are invalidated.
+ aView1.m_bTilesInvalidated = false;
+ aView2.m_bTilesInvalidated = false;
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DELETE);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DELETE);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(aView1.m_bTilesInvalidated);
+ CPPUNIT_ASSERT(aView2.m_bTilesInvalidated);
+}
+
+void SwTiledRenderingTest::testViewCursors()
+{
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ ViewCallback aView1(SfxViewShell::Current());
+ SfxLokHelper::createView();
+ ViewCallback aView2(SfxViewShell::Current());
+ CPPUNIT_ASSERT(aView1.m_bOwnCursorInvalidated);
+ CPPUNIT_ASSERT(aView1.m_bViewCursorInvalidated);
+ CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated);
+ // This failed: the cursor position of view1 was only known to view2 once
+ // it changed.
+ CPPUNIT_ASSERT(aView2.m_bViewCursorInvalidated);
+
+ // Make sure that aView1 gets a view-only selection notification, while
+ // aView2 gets a real selection notification.
+ aView1.m_bOwnSelectionSet = false;
+ aView1.m_bViewSelectionSet = false;
+ aView2.m_bOwnSelectionSet = false;
+ aView2.m_bViewSelectionSet = false;
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ // Move the cursor into the second word.
+ pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 5, /*bBasicCall=*/false);
+ // Create a selection on the word.
+ pWrtShell->SelWrd();
+ SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
+ // Did we indeed manage to select the second word?
+ CPPUNIT_ASSERT_EQUAL(OUString("bbb"), pShellCursor->GetText());
+ CPPUNIT_ASSERT(!aView1.m_bOwnSelectionSet);
+ // This failed, aView1 did not get notification about selection changes in
+ // aView2.
+ CPPUNIT_ASSERT(aView1.m_bViewSelectionSet);
+ CPPUNIT_ASSERT(aView2.m_bOwnSelectionSet);
+ CPPUNIT_ASSERT(!aView2.m_bViewSelectionSet);
+}
+
+void SwTiledRenderingTest::testShapeViewCursors()
+{
+ // Load a document and create a view, so we have 2 ones.
+ SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
+ ViewCallback aView1(SfxViewShell::Current());
+ SfxLokHelper::createView();
+ pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2(SfxViewShell::Current());
+ SwWrtShell* pWrtShell2 = pXTextDocument->GetDocShell()->GetWrtShell();
+
+ // Start shape text in the second view.
+ SdrPage* pPage = pWrtShell2->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
+ SdrObject* pObject = pPage->GetObj(0);
+ SdrView* pView = pWrtShell2->GetDrawView();
+ pWrtShell2->GetView().BeginTextEdit(pObject, pView->GetSdrPageView(), pWrtShell2->GetWin());
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ Scheduler::ProcessEventsToIdle();
+ // Press a key in the second view, while the first one observes this.
+ aView1.m_bViewCursorInvalidated = false;
+ aView2.m_bOwnCursorInvalidated = false;
+ const tools::Rectangle aLastOwnCursor1 = aView1.m_aOwnCursor;
+ const tools::Rectangle aLastViewCursor1 = aView1.m_aViewCursor;
+ const tools::Rectangle aLastOwnCursor2 = aView2.m_aOwnCursor;
+ const tools::Rectangle aLastViewCursor2 = aView2.m_aViewCursor;
+
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'y', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'y', 0);
+ Scheduler::ProcessEventsToIdle();
+ // Make sure that aView1 gets a view-only cursor notification, while
+ // aView2 gets a real cursor notification.
+ CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor, aLastOwnCursor1);
+ CPPUNIT_ASSERT(aView1.m_bViewCursorInvalidated && aLastViewCursor1 != aView1.m_aViewCursor);
+ CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated && aLastOwnCursor2 != aView2.m_aOwnCursor);
+ CPPUNIT_ASSERT_EQUAL(aLastViewCursor2, aView2.m_aViewCursor);
+}
+
+void SwTiledRenderingTest::testViewCursorVisibility()
+{
+ // Load a document that has a shape and create two views.
+ SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
+ ViewCallback aView1(SfxViewShell::Current());
+ SfxLokHelper::createView();
+ pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2(SfxViewShell::Current());
+ // This failed, initially the view cursor in the second view wasn't visible.
+ CPPUNIT_ASSERT(aView2.m_bViewCursorVisible);
+
+ // Click on the shape in the second view.
+ aView1.m_bViewCursorVisible = true;
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
+ SdrObject* pObject = pPage->GetObj(0);
+ Point aCenter = pObject->GetSnapRect().Center();
+ pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aCenter.getX(), aCenter.getY(), 1, MOUSE_LEFT, 0);
+ pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, aCenter.getX(), aCenter.getY(), 1, MOUSE_LEFT, 0);
+ Scheduler::ProcessEventsToIdle();
+ // Make sure the "view/text" cursor of the first view gets a notification.
+ CPPUNIT_ASSERT(!aView1.m_bViewCursorVisible);
+}
+
+void SwTiledRenderingTest::testViewCursorCleanup()
+{
+ // Load a document that has a shape and create two views.
+ SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
+ ViewCallback aView1(SfxViewShell::Current());
+ int nView2 = SfxLokHelper::createView();
+ pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ {
+ ViewCallback aView2(SfxViewShell::Current());
+
+ // Click on the shape in the second view.
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
+ SdrObject* pObject = pPage->GetObj(0);
+ Point aCenter = pObject->GetSnapRect().Center();
+ aView1.m_bGraphicViewSelection = false;
+ pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aCenter.getX(), aCenter.getY(), 1, MOUSE_LEFT, 0);
+ pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, aCenter.getX(), aCenter.getY(), 1, MOUSE_LEFT, 0);
+ Scheduler::ProcessEventsToIdle();
+ // Make sure there is a graphic view selection on the first view.
+ CPPUNIT_ASSERT(aView1.m_bGraphicViewSelection);
+ }
+ // Now destroy the second view.
+ SfxLokHelper::destroyView(nView2);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(1), SfxLokHelper::getViewsCount());
+ // Make sure that the graphic view selection on the first view is cleaned up.
+ CPPUNIT_ASSERT(!aView1.m_bGraphicViewSelection);
+}
+
+void SwTiledRenderingTest::testViewLock()
+{
+ // Load a document that has a shape and create two views.
+ SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
+ ViewCallback aView1(SfxViewShell::Current());
+ SfxLokHelper::createView();
+ pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2(SfxViewShell::Current());
+
+ // Begin text edit in the second view and assert that the first gets a lock
+ // notification.
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
+ SdrObject* pObject = pPage->GetObj(0);
+ SdrView* pView = pWrtShell->GetDrawView();
+ aView1.m_bViewLock = false;
+ pWrtShell->GetView().BeginTextEdit(pObject, pView->GetSdrPageView(), pWrtShell->GetWin());
+ CPPUNIT_ASSERT(aView1.m_bViewLock);
+
+ // End text edit in the second view, and assert that the lock is removed in
+ // the first view.
+ pWrtShell->EndTextEdit();
+ CPPUNIT_ASSERT(!aView1.m_bViewLock);
+}
+
+void SwTiledRenderingTest::testTextEditViewInvalidations()
+{
+ // Load a document that has a shape and create two views.
+ SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
+ ViewCallback aView1(SfxViewShell::Current());
+ SfxLokHelper::createView();
+ pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2(SfxViewShell::Current());
+
+ // Begin text edit in the second view.
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
+ SdrObject* pObject = pPage->GetObj(0);
+ SdrView* pView = pWrtShell->GetDrawView();
+ pWrtShell->GetView().BeginTextEdit(pObject, pView->GetSdrPageView(), pWrtShell->GetWin());
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ Scheduler::ProcessEventsToIdle();
+
+ // Assert that both views are invalidated when pressing a key while in text edit.
+ aView1.m_bTilesInvalidated = false;
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'y', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'y', 0);
+ Scheduler::ProcessEventsToIdle();
+
+ CPPUNIT_ASSERT(aView1.m_bTilesInvalidated);
+
+ pWrtShell->EndTextEdit();
+}
+
+void SwTiledRenderingTest::testUndoInvalidations()
+{
+ // Load a document and create two views.
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ ViewCallback aView1(SfxViewShell::Current());
+ int nView1 = SfxLokHelper::getView();
+ SfxLokHelper::createView();
+ pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2(SfxViewShell::Current());
+ SfxLokHelper::setView(nView1);
+
+ // Insert a character the end of the document.
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->EndOfSection();
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'c', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'c', 0);
+ Scheduler::ProcessEventsToIdle();
+ // ProcessEventsToIdle resets the view; set it again
+ SfxLokHelper::setView(nView1);
+ SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
+ CPPUNIT_ASSERT_EQUAL(OUString("Aaa bbb.c"), pShellCursor->GetPoint()->nNode.GetNode().GetTextNode()->GetText());
+
+ // Undo and assert that both views are invalidated.
+ aView1.m_bTilesInvalidated = false;
+ aView2.m_bTilesInvalidated = false;
+ comphelper::dispatchCommand(".uno:Undo", {});
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(aView1.m_bTilesInvalidated);
+ // Undo was dispatched on the first view, this second view was not invalidated.
+ CPPUNIT_ASSERT(aView2.m_bTilesInvalidated);
+}
+
+void SwTiledRenderingTest::testUndoLimiting()
+{
+ // Load a document and create two views.
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ SwWrtShell* pWrtShell1 = pXTextDocument->GetDocShell()->GetWrtShell();
+ int nView1 = SfxLokHelper::getView();
+ int nView2 = SfxLokHelper::createView();
+ pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+
+ // Insert a character the end of the document in the second view.
+ SwWrtShell* pWrtShell2 = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell2->EndOfSection();
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'c', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'c', 0);
+ Scheduler::ProcessEventsToIdle();
+ SwShellCursor* pShellCursor = pWrtShell2->getShellCursor(false);
+ CPPUNIT_ASSERT_EQUAL(OUString("Aaa bbb.c"), pShellCursor->GetPoint()->nNode.GetNode().GetTextNode()->GetText());
+
+ // Assert that the first view can't undo, but the second view can.
+ CPPUNIT_ASSERT(!pWrtShell1->GetLastUndoInfo(nullptr, nullptr, &pWrtShell1->GetView()));
+ CPPUNIT_ASSERT(pWrtShell2->GetLastUndoInfo(nullptr, nullptr, &pWrtShell2->GetView()));
+
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+ SfxLokHelper::setView(nView2);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+}
+
+void SwTiledRenderingTest::testUndoShapeLimiting()
+{
+ // Load a document and create a view.
+ SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
+ SwWrtShell* pWrtShell1 = pXTextDocument->GetDocShell()->GetWrtShell();
+ int nView1 = SfxLokHelper::getView();
+ int nView2 = SfxLokHelper::createView();
+ pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ SwWrtShell* pWrtShell2 = pXTextDocument->GetDocShell()->GetWrtShell();
+
+ // Start shape text in the second view.
+ SdrPage* pPage = pWrtShell2->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
+ SdrObject* pObject = pPage->GetObj(0);
+ SdrView* pView = pWrtShell2->GetDrawView();
+ pWrtShell2->GetView().BeginTextEdit(pObject, pView->GetSdrPageView(), pWrtShell2->GetWin());
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ Scheduler::ProcessEventsToIdle();
+
+ // Assert that the first view can't and the second view can undo the insertion.
+ SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc();
+ sw::UndoManager& rUndoManager = pDoc->GetUndoManager();
+ rUndoManager.SetView(&pWrtShell1->GetView());
+ // This was 1: first view could undo the change of the second view.
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), rUndoManager.GetUndoActionCount());
+ rUndoManager.SetView(&pWrtShell2->GetView());
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rUndoManager.GetUndoActionCount());
+
+ pWrtShell2->EndTextEdit();
+ rUndoManager.SetView(nullptr);
+
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+ SfxLokHelper::setView(nView2);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+}
+
+void SwTiledRenderingTest::testUndoDispatch()
+{
+ // Load a document and create two views.
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ int nView1 = SfxLokHelper::getView();
+ SfxLokHelper::createView();
+ pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ int nView2 = SfxLokHelper::getView();
+
+ // Insert a character in the first view.
+ SfxLokHelper::setView(nView1);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'c', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'c', 0);
+ Scheduler::ProcessEventsToIdle();
+
+ // Click before the first word in the second view.
+ SfxLokHelper::setView(nView2);
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
+ Point aStart = pShellCursor->GetSttPos();
+ aStart.setX(aStart.getX() - 1000);
+ pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aStart.getX(), aStart.getY(), 1, MOUSE_LEFT, 0);
+ pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, aStart.getX(), aStart.getY(), 1, MOUSE_LEFT, 0);
+ Scheduler::ProcessEventsToIdle();
+ uno::Reference<frame::XDesktop2> xDesktop = frame::Desktop::create(comphelper::getProcessComponentContext());
+ uno::Reference<frame::XFrame> xFrame2 = xDesktop->getActiveFrame();
+
+ // Now switch back to the first view, and make sure that the active frame is updated.
+ SfxLokHelper::setView(nView1);
+ uno::Reference<frame::XFrame> xFrame1 = xDesktop->getActiveFrame();
+ // This failed: setView() did not update the active frame.
+ CPPUNIT_ASSERT(xFrame1 != xFrame2);
+
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+ SfxLokHelper::setView(nView2);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+}
+
+void SwTiledRenderingTest::testUndoRepairDispatch()
+{
+ // Load a document and create two views.
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ int nView1 = SfxLokHelper::getView();
+ SfxLokHelper::createView();
+ pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ int nView2 = SfxLokHelper::getView();
+
+ // Insert a character in the first view.
+ SfxLokHelper::setView(nView1);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'c', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'c', 0);
+ Scheduler::ProcessEventsToIdle();
+
+ // Assert that by default the second view can't undo the action.
+ SfxLokHelper::setView(nView2);
+ SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc();
+ sw::UndoManager& rUndoManager = pDoc->GetUndoManager();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rUndoManager.GetUndoActionCount());
+ comphelper::dispatchCommand(".uno:Undo", {});
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rUndoManager.GetUndoActionCount());
+
+ // But the same is allowed in repair mode.
+ SfxLokHelper::setView(nView2);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rUndoManager.GetUndoActionCount());
+ uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
+ {
+ {"Repair", uno::makeAny(true)}
+ }));
+ comphelper::dispatchCommand(".uno:Undo", aPropertyValues);
+ Scheduler::ProcessEventsToIdle();
+ // This was 1: repair mode couldn't undo the action, either.
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), rUndoManager.GetUndoActionCount());
+
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+ SfxLokHelper::setView(nView2);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+}
+
+void SwTiledRenderingTest::testShapeTextUndoShells()
+{
+ // Load a document and create a view.
+ SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
+ sal_Int32 nView1 = SfxLokHelper::getView();
+
+ // Begin text edit.
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
+ SdrObject* pObject = pPage->GetObj(0);
+ SdrView* pView = pWrtShell->GetDrawView();
+ pWrtShell->GetView().BeginTextEdit(pObject, pView->GetSdrPageView(), pWrtShell->GetWin());
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ Scheduler::ProcessEventsToIdle();
+
+ // Make sure that the undo item remembers who created it.
+ SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc();
+ sw::UndoManager& rUndoManager = pDoc->GetUndoManager();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rUndoManager.GetUndoActionCount());
+ // This was -1: the view shell id for the undo action wasn't known.
+ CPPUNIT_ASSERT_EQUAL(ViewShellId(nView1), rUndoManager.GetUndoAction()->GetViewShellId());
+}
+
+void SwTiledRenderingTest::testShapeTextUndoGroupShells()
+{
+ // Load a document and create a view.
+ SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
+ ViewCallback aView1(SfxViewShell::Current());
+ sal_Int32 nView1 = SfxLokHelper::getView();
+
+ // Begin text edit.
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
+ SdrObject* pObject = pPage->GetObj(0);
+ SdrView* pView = pWrtShell->GetDrawView();
+ pWrtShell->GetView().BeginTextEdit(pObject, pView->GetSdrPageView(), pWrtShell->GetWin());
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::BACKSPACE);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::BACKSPACE);
+ Scheduler::ProcessEventsToIdle();
+
+ // Make sure that the undo item remembers who created it.
+ SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc();
+ sw::UndoManager& rUndoManager = pDoc->GetUndoManager();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), rUndoManager.GetUndoActionCount());
+ // This was -1: the view shell id for the (top) undo list action wasn't known.
+ CPPUNIT_ASSERT_EQUAL(ViewShellId(nView1), rUndoManager.GetUndoAction()->GetViewShellId());
+
+ // Create an editeng text selection in the first view.
+ EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView();
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ Scheduler::ProcessEventsToIdle();
+ // 0th para, 0th char -> 0th para, 1st char.
+ ESelection aWordSelection(0, 0, 0, 1);
+ rEditView.SetSelection(aWordSelection);
+
+ // Create a second view, and make sure that the new view sees the same
+ // cursor position as the old one.
+ SfxLokHelper::createView();
+ pXTextDocument->initializeForTiledRendering({});
+ ViewCallback aView2(SfxViewShell::Current());
+ // Difference was 935 twips, the new view didn't see the editeng cursor of
+ // the old one. The new difference should be <1px, but here we deal with twips.
+ CPPUNIT_ASSERT(std::abs(aView1.m_aOwnCursor.Top() - aView2.m_aViewCursor.Top()) < 10);
+ // This was false, editeng text selection of the first view wasn't noticed
+ // by the second view.
+ CPPUNIT_ASSERT(aView2.m_bViewSelectionSet);
+ // This was false, the new view wasn't aware of the shape text lock created
+ // by the old view.
+ CPPUNIT_ASSERT(aView2.m_bViewLock);
+}
+
+void SwTiledRenderingTest::testTrackChanges()
+{
+ // Load a document.
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+
+ // Turn on track changes, type "zzz" at the end, and move to the start.
+ uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
+ xPropertySet->setPropertyValue("RecordChanges", uno::makeAny(true));
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ ViewCallback aView(pWrtShell->GetSfxViewShell());
+ pWrtShell->EndOfSection();
+ pWrtShell->Insert("zzz");
+ pWrtShell->StartOfSection();
+
+ // Get the redline just created
+ const SwRedlineTable& rTable = pWrtShell->GetDoc()->getIDocumentRedlineAccess().GetRedlineTable();
+ CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(1), rTable.size());
+ SwRangeRedline* pRedline = rTable[0];
+
+ // Reject the change by id, while the cursor does not cover the tracked change.
+ uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
+ {
+ {"RejectTrackedChange", uno::makeAny(static_cast<sal_uInt16>(pRedline->GetId()))}
+ }));
+ comphelper::dispatchCommand(".uno:RejectTrackedChange", aPropertyValues);
+ Scheduler::ProcessEventsToIdle();
+
+ // Assert that the reject was performed.
+ SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
+ // This was 'Aaa bbb.zzz', the change wasn't rejected.
+ CPPUNIT_ASSERT_EQUAL(OUString("Aaa bbb."), pShellCursor->GetPoint()->nNode.GetNode().GetTextNode()->GetText());
+}
+
+void SwTiledRenderingTest::testTrackChangesCallback()
+{
+ // Load a document.
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+
+ // Turn on track changes and type "x".
+ uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
+ xPropertySet->setPropertyValue("RecordChanges", uno::makeAny(true));
+ m_nRedlineTableSizeChanged = 0;
+ pWrtShell->Insert("x");
+
+ // Assert that we get exactly one notification about the redline insert.
+ // This was 0, as LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED wasn't sent.
+ CPPUNIT_ASSERT_EQUAL(1, m_nRedlineTableSizeChanged);
+
+ CPPUNIT_ASSERT_EQUAL(-1, m_nTrackedChangeIndex);
+ pWrtShell->Left(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false);
+ SfxItemSet aSet(pWrtShell->GetDoc()->GetAttrPool(), svl::Items<FN_REDLINE_ACCEPT_DIRECT, FN_REDLINE_ACCEPT_DIRECT>{});
+ SfxVoidItem aItem(FN_REDLINE_ACCEPT_DIRECT);
+ aSet.Put(aItem);
+ pWrtShell->GetView().GetState(aSet);
+ // This failed, LOK_CALLBACK_STATE_CHANGED wasn't sent.
+ CPPUNIT_ASSERT_EQUAL(0, m_nTrackedChangeIndex);
+}
+
+void SwTiledRenderingTest::testRedlineUpdateCallback()
+{
+ // Load a document.
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+
+ // Turn on track changes, type "xx" and delete the second one.
+ uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
+ xPropertySet->setPropertyValue("RecordChanges", uno::makeAny(true));
+ pWrtShell->Insert("xx");
+ m_nRedlineTableEntryModified = 0;
+ pWrtShell->DelLeft();
+
+ // Assert that we get exactly one notification about the redline update.
+ // This was 0, as LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED wasn't sent.
+ CPPUNIT_ASSERT_EQUAL(1, m_nRedlineTableEntryModified);
+
+ // Turn off the change tracking mode, make some modification to left of the
+ // redline so that its position changes
+ xPropertySet->setPropertyValue("RecordChanges", uno::makeAny(false));
+ pWrtShell->Left(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false);
+ pWrtShell->Insert("This text is left of the redline");
+
+ // Position of the redline has changed => Modify callback
+ CPPUNIT_ASSERT_EQUAL(2, m_nRedlineTableEntryModified);
+
+ pWrtShell->DelLeft();
+ // Deletion also emits Modify callback
+ CPPUNIT_ASSERT_EQUAL(3, m_nRedlineTableEntryModified);
+
+ // Make changes to the right of the redline => no position change in redline
+ pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 100/*Go enough right */, /*bBasicCall=*/false);
+ pWrtShell->Insert("This text is right of the redline");
+
+ // No Modify callbacks
+ CPPUNIT_ASSERT_EQUAL(3, m_nRedlineTableEntryModified);
+}
+
+void SwTiledRenderingTest::testSetViewGraphicSelection()
+{
+ // Load a document.
+ SwXTextDocument* pXTextDocument = createDoc("frame.odt");
+ int nView1 = SfxLokHelper::getView();
+ ViewCallback aView1(SfxViewShell::Current());
+ // Create a second view, and switch back to the first view.
+ SfxLokHelper::createView();
+ pXTextDocument->initializeForTiledRendering({});
+ SfxLokHelper::setView(nView1);
+
+ // Mark the textframe in the first view.
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
+ SdrObject* pObject = pPage->GetObj(0);
+ SdrView* pView = pWrtShell->GetDrawView();
+ pView->MarkObj(pObject, pView->GetSdrPageView());
+ CPPUNIT_ASSERT(aView1.m_bGraphicSelection);
+
+ // Now start to switch to the second view (part of setView()).
+ pWrtShell->ShellLoseFocus();
+ // This failed, mark handles were hidden in the first view.
+ CPPUNIT_ASSERT(!pView->areMarkHandlesHidden());
+}
+
+void SwTiledRenderingTest::testCreateViewGraphicSelection()
+{
+ // Load a document.
+ SwXTextDocument* pXTextDocument = createDoc("frame.odt");
+ ViewCallback aView1(SfxViewShell::Current());
+
+ // Mark the textframe in the first view.
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
+ SdrObject* pObject = pPage->GetObj(0);
+ SdrView* pView = pWrtShell->GetDrawView();
+ aView1.m_bGraphicSelection = true;
+ pView->MarkObj(pObject, pView->GetSdrPageView());
+ pWrtShell->HideCursor();
+ CPPUNIT_ASSERT(aView1.m_bGraphicSelection);
+
+ // Create a second view.
+ SfxLokHelper::createView();
+ // This was false, creating a second view cleared the selection of the
+ // first one.
+ CPPUNIT_ASSERT(aView1.m_bGraphicSelection);
+
+ // Make sure that the hidden text cursor isn't visible in the second view, either.
+ ViewCallback aView2(SfxViewShell::Current(),
+ [](ViewCallback& rView) { rView.m_bViewCursorVisible = true; });
+ // This was true, the second view didn't get the visibility of the text
+ // cursor of the first view.
+ CPPUNIT_ASSERT(!aView2.m_bViewCursorVisible);
+ // This was false, the second view didn't get the graphic selection of the
+ // first view.
+ CPPUNIT_ASSERT(aView2.m_bGraphicViewSelection);
+}
+
+void SwTiledRenderingTest::testCreateViewTextSelection()
+{
+ // Load a document.
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ ViewCallback aView1(SfxViewShell::Current());
+
+ // Create a text selection:
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ // Move the cursor into the second word.
+ pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 5, /*bBasicCall=*/false);
+ // Create a selection on the word.
+ pWrtShell->SelWrd();
+ SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
+ // Did we indeed manage to select the second word?
+ CPPUNIT_ASSERT_EQUAL(OUString("bbb"), pShellCursor->GetText());
+
+ // Create a second view.
+ SfxLokHelper::createView();
+
+ // Make sure that the text selection is visible in the second view.
+ ViewCallback aView2(SfxViewShell::Current());
+ // This failed, the second view didn't get the text selection of the first view.
+ CPPUNIT_ASSERT(!aView2.m_aViewSelection.isEmpty());
+}
+
+void SwTiledRenderingTest::testRedlineColors()
+{
+ // Load a document.
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+
+ // Turn on track changes, type "zzz" at the end.
+ uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
+ xPropertySet->setPropertyValue("RecordChanges", uno::makeAny(true));
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->EndOfSection();
+ pWrtShell->Insert("zzz");
+
+ // Assert that info about exactly one author is returned.
+ OUString aInfo = pXTextDocument->getTrackedChangeAuthors();
+ std::stringstream aStream(aInfo.toUtf8().getStr());
+ boost::property_tree::ptree aTree;
+ boost::property_tree::read_json(aStream, aTree);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aTree.get_child("authors").size());
+}
+
+void SwTiledRenderingTest::testCommentEndTextEdit()
+{
+ // Create a document, type a character and remember the cursor position.
+ SwXTextDocument* pXTextDocument = createDoc();
+ ViewCallback aView1(SfxViewShell::Current());
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ Scheduler::ProcessEventsToIdle();
+ tools::Rectangle aBodyCursor = aView1.m_aOwnCursor;
+
+ // Create a comment and type a character there as well.
+ const int nCtrlAltC = KEY_MOD1 + KEY_MOD2 + 512 + 'c' - 'a';
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'c', nCtrlAltC);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'c', nCtrlAltC);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ Scheduler::ProcessEventsToIdle();
+ // End comment text edit by clicking in the body text area, and assert that
+ // no unexpected cursor callbacks are emitted at origin (top left corner of
+ // the document).
+ aView1.m_bOwnCursorAtOrigin = false;
+ pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aBodyCursor.getX(), aBodyCursor.getY(), 1, MOUSE_LEFT, 0);
+ pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, aBodyCursor.getX(), aBodyCursor.getY(), 1, MOUSE_LEFT, 0);
+ Scheduler::ProcessEventsToIdle();
+ // This failed, the cursor was at 0, 0 at some point during end text edit
+ // of the comment.
+ CPPUNIT_ASSERT(!aView1.m_bOwnCursorAtOrigin);
+
+ // Hit enter and expect invalidation.
+ Scheduler::ProcessEventsToIdle();
+ aView1.m_bTilesInvalidated = false;
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RETURN);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(aView1.m_bTilesInvalidated);
+}
+
+void SwTiledRenderingTest::testCommentInsert()
+{
+ // Load a document with an as-char image in it.
+ comphelper::LibreOfficeKit::setTiledAnnotations(false);
+ SwXTextDocument* pXTextDocument = createDoc("image-comment.odt");
+ SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc();
+ SwView* pView = pDoc->GetDocShell()->GetView();
+
+ // Select the image.
+ pView->GetViewFrame()->GetDispatcher()->Execute(FN_CNTNT_TO_NEXT_FRAME, SfxCallMode::SYNCHRON);
+ // Make sure SwTextShell is replaced with SwDrawShell right now, not after 120 ms, as set in the
+ // SwView ctor.
+ pView->StopShellTimer();
+
+ // Add a comment.
+ uno::Reference<frame::XFrame> xFrame = pView->GetViewFrame()->GetFrame().GetFrameInterface();
+ uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence(
+ {
+ {"Text", uno::makeAny(OUString("some text"))},
+ {"Author", uno::makeAny(OUString("me"))},
+ });
+ ViewCallback aView(SfxViewShell::Current());
+ comphelper::dispatchCommand(".uno:InsertAnnotation", xFrame, aPropertyValues);
+ Scheduler::ProcessEventsToIdle();
+ OString aAnchorPos(aView.m_aComment.get_child("anchorPos").get_value<std::string>().c_str());
+ // Without the accompanying fix in place, this test would have failed with
+ // - Expected: 1418, 1418, 0, 0
+ // - Actual : 1418, 1418, 1024, 1024
+ // i.e. the anchor position was a non-empty rectangle.
+ CPPUNIT_ASSERT_EQUAL(OString("1418, 1418, 0, 0"), aAnchorPos);
+ comphelper::LibreOfficeKit::setTiledAnnotations(true);
+}
+
+void SwTiledRenderingTest::testCursorPosition()
+{
+ // Load a document and register a callback, should get an own cursor.
+ SwXTextDocument* pXTextDocument = createDoc();
+ ViewCallback aView1(SfxViewShell::Current());
+
+ // Crete a second view, so the first view gets a collaborative cursor.
+ SfxLokHelper::createView();
+ pXTextDocument->initializeForTiledRendering({});
+ ViewCallback aView2(SfxViewShell::Current());
+
+ // Make sure the two are exactly the same.
+ // This failed, own cursor was at '1418, 1418', collaborative cursor was at
+ // '1425, 1425', due to pixel alignment.
+ CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor.toString(), aView1.m_aViewCursor.toString());
+}
+
+void SwTiledRenderingTest::testPaintCallbacks()
+{
+ // Test that paintTile() never results in callbacks, which can cause a
+ // paint <-> invalidate loop.
+
+ // Load a document and register a callback for the first view.
+ SwXTextDocument* pXTextDocument = createDoc();
+ ViewCallback aView1(SfxViewShell::Current());
+
+ // Create a second view and paint a tile on that second view.
+ SfxLokHelper::createView();
+ int nCanvasWidth = 256;
+ int nCanvasHeight = 256;
+ std::vector<unsigned char> aBuffer(nCanvasWidth * nCanvasHeight * 4);
+ ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::DEFAULT);
+ pDevice->SetOutputSizePixelScaleOffsetAndBuffer(Size(nCanvasWidth, nCanvasHeight), Fraction(1.0), Point(), aBuffer.data());
+ // Make sure that painting a tile in the second view doesn't invoke
+ // callbacks on the first view.
+ aView1.m_bCalled = false;
+ pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, /*nTilePosY=*/0, /*nTileWidth=*/3840, /*nTileHeight=*/3840);
+ CPPUNIT_ASSERT(!aView1.m_bCalled);
+}
+
+void SwTiledRenderingTest::testUndoRepairResult()
+{
+ // Load a document and create two views.
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ int nView1 = SfxLokHelper::getView();
+ SfxLokHelper::createView();
+ TestResultListener* pResult2 = new TestResultListener();
+ css::uno::Reference< css::frame::XDispatchResultListener > xListener(static_cast< css::frame::XDispatchResultListener* >(pResult2), css::uno::UNO_QUERY);
+ pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ int nView2 = SfxLokHelper::getView();
+
+ // Insert a character in the second view.
+ SfxLokHelper::setView(nView2);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'b', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'b', 0);
+ Scheduler::ProcessEventsToIdle();
+
+ // Insert a character in the first view.
+ SfxLokHelper::setView(nView1);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'a', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'a', 0);
+ Scheduler::ProcessEventsToIdle();
+
+ // Assert that by default the second view can't undo the action.
+ SfxLokHelper::setView(nView2);
+ comphelper::dispatchCommand(".uno:Undo", {}, xListener);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(SID_REPAIRPACKAGE), pResult2->m_nDocRepair);
+
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+ SfxLokHelper::setView(nView2);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+}
+
+void SwTiledRenderingTest::testRedoRepairResult()
+{
+ // Load a document and create two views.
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ int nView1 = SfxLokHelper::getView();
+ SfxLokHelper::createView();
+ TestResultListener* pResult2 = new TestResultListener();
+ css::uno::Reference< css::frame::XDispatchResultListener > xListener(static_cast< css::frame::XDispatchResultListener* >(pResult2), css::uno::UNO_QUERY);
+ pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ int nView2 = SfxLokHelper::getView();
+
+ // Insert a character in the second view.
+ SfxLokHelper::setView(nView2);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'b', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'b', 0);
+ Scheduler::ProcessEventsToIdle();
+
+ // Insert a character in the first view.
+ SfxLokHelper::setView(nView1);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'a', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'a', 0);
+ Scheduler::ProcessEventsToIdle();
+
+ comphelper::dispatchCommand(".uno:Undo", {}, xListener);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(0), pResult2->m_nDocRepair);
+
+ // Assert that by default the second view can't redo the action.
+ SfxLokHelper::setView(nView2);
+ comphelper::dispatchCommand(".uno:Redo", {}, xListener);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(SID_REPAIRPACKAGE), pResult2->m_nDocRepair);
+
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+ SfxLokHelper::setView(nView2);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+}
+
+namespace {
+
+void checkUndoRepairStates(SwXTextDocument* pXTextDocument, SwView* pView1, SwView* pView2)
+{
+ SfxItemSet aItemSet1(pXTextDocument->GetDocShell()->GetDoc()->GetAttrPool(), svl::Items<SID_UNDO, SID_UNDO>{});
+ SfxItemSet aItemSet2(pXTextDocument->GetDocShell()->GetDoc()->GetAttrPool(), svl::Items<SID_UNDO, SID_UNDO>{});
+ // first view, undo enabled
+ pView1->GetState(aItemSet1);
+ CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aItemSet1.GetItemState(SID_UNDO));
+ const SfxUInt32Item *pUnsetItem = dynamic_cast<const SfxUInt32Item*>(aItemSet1.GetItem(SID_UNDO));
+ CPPUNIT_ASSERT(!pUnsetItem);
+ // second view, undo conflict
+ pView2->GetState(aItemSet2);
+ CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aItemSet2.GetItemState(SID_UNDO));
+ const SfxUInt32Item* pUInt32Item = dynamic_cast<const SfxUInt32Item*>(aItemSet2.GetItem(SID_UNDO));
+ CPPUNIT_ASSERT(pUInt32Item);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(SID_REPAIRPACKAGE), pUInt32Item->GetValue());
+};
+
+}
+
+void SwTiledRenderingTest::testDisableUndoRepair()
+{
+ // Create two views.
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ ViewCallback aView1(SfxViewShell::Current());
+ SwView* pView1 = dynamic_cast<SwView*>(SfxViewShell::Current());
+ CPPUNIT_ASSERT(pView1);
+ int nView1 = SfxLokHelper::getView();
+ SfxLokHelper::createView();
+ ViewCallback aView2(SfxViewShell::Current());
+ SwView* pView2 = dynamic_cast<SwView*>(SfxViewShell::Current());
+ CPPUNIT_ASSERT(pView2);
+ int nView2 = SfxLokHelper::getView();
+
+ {
+ SfxItemSet aItemSet1(pXTextDocument->GetDocShell()->GetDoc()->GetAttrPool(), svl::Items<SID_UNDO, SID_UNDO>{});
+ SfxItemSet aItemSet2(pXTextDocument->GetDocShell()->GetDoc()->GetAttrPool(), svl::Items<SID_UNDO, SID_UNDO>{});
+ pView1->GetState(aItemSet1);
+ CPPUNIT_ASSERT_EQUAL(SfxItemState::DISABLED, aItemSet1.GetItemState(SID_UNDO));
+ pView2->GetState(aItemSet2);
+ CPPUNIT_ASSERT_EQUAL(SfxItemState::DISABLED, aItemSet2.GetItemState(SID_UNDO));
+ }
+
+ // Insert a character in the first view.
+ SfxLokHelper::setView(nView1);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'k', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'k', 0);
+ Scheduler::ProcessEventsToIdle();
+ checkUndoRepairStates(pXTextDocument, pView1, pView2);
+
+ // Insert a character in the second view.
+ SfxLokHelper::setView(nView2);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'u', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'u', 0);
+ Scheduler::ProcessEventsToIdle();
+ {
+ SfxItemSet aItemSet1(pXTextDocument->GetDocShell()->GetDoc()->GetAttrPool(), svl::Items<SID_UNDO, SID_UNDO>{});
+ SfxItemSet aItemSet2(pXTextDocument->GetDocShell()->GetDoc()->GetAttrPool(), svl::Items<SID_UNDO, SID_UNDO>{});
+ // second view, undo enabled
+ pView2->GetState(aItemSet2);
+ CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aItemSet2.GetItemState(SID_UNDO));
+ const SfxUInt32Item *pUnsetItem = dynamic_cast<const SfxUInt32Item*>(aItemSet2.GetItem(SID_UNDO));
+ CPPUNIT_ASSERT(!pUnsetItem);
+ // first view, undo conflict
+ pView1->GetState(aItemSet1);
+ CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aItemSet1.GetItemState(SID_UNDO));
+ const SfxUInt32Item* pUInt32Item = dynamic_cast<const SfxUInt32Item*>(aItemSet1.GetItem(SID_UNDO));
+ CPPUNIT_ASSERT(pUInt32Item);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(SID_REPAIRPACKAGE), pUInt32Item->GetValue());
+ }
+
+ // Insert a character in the first view.
+ SfxLokHelper::setView(nView1);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'l', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'l', 0);
+ Scheduler::ProcessEventsToIdle();
+ checkUndoRepairStates(pXTextDocument, pView1, pView2);
+}
+
+void SwTiledRenderingTest::testAllTrackedChanges()
+{
+ // Load a document.
+ createDoc("dummy.fodt");
+
+ uno::Reference<beans::XPropertySet> xPropSet(mxComponent, uno::UNO_QUERY);
+ xPropSet->setPropertyValue("RecordChanges", uno::makeAny(true));
+
+ // view #1
+ SwView* pView1 = dynamic_cast<SwView*>(SfxViewShell::Current());
+ CPPUNIT_ASSERT(pView1);
+ SwWrtShell* pWrtShell1 = pView1->GetWrtShellPtr();
+
+ // view #2
+ int nView1 = SfxLokHelper::getView();
+ int nView2 = SfxLokHelper::createView();
+ SwView* pView2 = dynamic_cast<SwView*>(SfxViewShell::Current());
+ CPPUNIT_ASSERT(pView2 && pView1 != pView2);
+ SwWrtShell* pWrtShell2 = pView2->GetWrtShellPtr();
+ // Insert text and reject all
+ {
+ pWrtShell1->StartOfSection();
+ pWrtShell1->Insert("hxx");
+
+ pWrtShell2->EndOfSection();
+ pWrtShell2->Insert("cxx");
+ }
+
+ // Get the redline
+ const SwRedlineTable& rTable = pWrtShell2->GetDoc()->getIDocumentRedlineAccess().GetRedlineTable();
+ CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(2), rTable.size());
+ {
+ SfxVoidItem aItem(FN_REDLINE_REJECT_ALL);
+ pView1->GetViewFrame()->GetDispatcher()->ExecuteList(FN_REDLINE_REJECT_ALL,
+ SfxCallMode::SYNCHRON, { &aItem });
+ }
+
+ // The reject all was performed.
+ CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(0), rTable.size());
+ {
+ SwShellCursor* pShellCursor = pWrtShell1->getShellCursor(false);
+ CPPUNIT_ASSERT_EQUAL(OUString("Aaa bbb."), pShellCursor->GetPoint()->nNode.GetNode().GetTextNode()->GetText());
+ }
+
+ // Insert text and accept all
+ {
+ pWrtShell1->StartOfSection();
+ pWrtShell1->Insert("hyy");
+
+ pWrtShell2->EndOfSection();
+ pWrtShell2->Insert("cyy");
+ }
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(2), rTable.size());
+ {
+ SfxVoidItem aItem(FN_REDLINE_ACCEPT_ALL);
+ pView1->GetViewFrame()->GetDispatcher()->ExecuteList(FN_REDLINE_ACCEPT_ALL,
+ SfxCallMode::SYNCHRON, { &aItem });
+ }
+
+ // The accept all was performed
+ CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(0), rTable.size());
+ {
+ SwShellCursor* pShellCursor = pWrtShell2->getShellCursor(false);
+ CPPUNIT_ASSERT_EQUAL(OUString("hyyAaa bbb.cyy"), pShellCursor->GetPoint()->nNode.GetNode().GetTextNode()->GetText());
+ }
+
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+ SfxLokHelper::setView(nView2);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+}
+
+void SwTiledRenderingTest::testDocumentRepair()
+{
+ // Create two views.
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ // view #1
+ SfxViewShell* pView1 = SfxViewShell::Current();
+
+ // view #2
+ int nView1 = SfxLokHelper::getView();
+ SfxLokHelper::createView();
+ SfxViewShell* pView2 = SfxViewShell::Current();
+ int nView2 = SfxLokHelper::getView();
+ CPPUNIT_ASSERT(pView1 != pView2);
+ {
+ std::unique_ptr<SfxPoolItem> xItem1;
+ std::unique_ptr<SfxPoolItem> xItem2;
+ pView1->GetViewFrame()->GetBindings().QueryState(SID_DOC_REPAIR, xItem1);
+ pView2->GetViewFrame()->GetBindings().QueryState(SID_DOC_REPAIR, xItem2);
+ const SfxBoolItem* pItem1 = dynamic_cast<const SfxBoolItem*>(xItem1.get());
+ const SfxBoolItem* pItem2 = dynamic_cast<const SfxBoolItem*>(xItem2.get());
+ CPPUNIT_ASSERT(pItem1);
+ CPPUNIT_ASSERT(pItem2);
+ CPPUNIT_ASSERT_EQUAL(false, pItem1->GetValue());
+ CPPUNIT_ASSERT_EQUAL(false, pItem2->GetValue());
+ }
+
+ // Insert a character in the second view.
+ SfxLokHelper::setView(nView2);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'u', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'u', 0);
+ Scheduler::ProcessEventsToIdle();
+ {
+ std::unique_ptr<SfxPoolItem> xItem1;
+ std::unique_ptr<SfxPoolItem> xItem2;
+ pView1->GetViewFrame()->GetBindings().QueryState(SID_DOC_REPAIR, xItem1);
+ pView2->GetViewFrame()->GetBindings().QueryState(SID_DOC_REPAIR, xItem2);
+ const SfxBoolItem* pItem1 = dynamic_cast<const SfxBoolItem*>(xItem1.get());
+ const SfxBoolItem* pItem2 = dynamic_cast<const SfxBoolItem*>(xItem2.get());
+ CPPUNIT_ASSERT(pItem1);
+ CPPUNIT_ASSERT(pItem2);
+ CPPUNIT_ASSERT_EQUAL(true, pItem1->GetValue());
+ CPPUNIT_ASSERT_EQUAL(true, pItem2->GetValue());
+ }
+
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+ SfxLokHelper::setView(nView2);
+ SfxViewShell::Current()->registerLibreOfficeKitViewCallback(nullptr, nullptr);
+}
+
+namespace {
+
+void checkPageHeaderOrFooter(const SfxViewShell* pViewShell, sal_uInt16 nWhich, bool bValue)
+{
+ uno::Sequence<OUString> aSeq;
+ const SfxPoolItem* pState = nullptr;
+ pViewShell->GetDispatcher()->QueryState(nWhich, pState);
+ const SfxStringListItem* pListItem = dynamic_cast<const SfxStringListItem*>(pState);
+ CPPUNIT_ASSERT(pListItem);
+ pListItem->GetStringList(aSeq);
+ if (bValue)
+ {
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aSeq.getLength());
+ CPPUNIT_ASSERT_EQUAL(OUString("Default Page Style"), aSeq[0]);
+ }
+ else
+ CPPUNIT_ASSERT(!aSeq.hasElements());
+};
+
+}
+
+void SwTiledRenderingTest::testPageHeader()
+{
+ createDoc("dummy.fodt");
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ // Check Page Header State
+ checkPageHeaderOrFooter(pViewShell, FN_INSERT_PAGEHEADER, false);
+ // Insert Page Header
+ {
+ SfxStringItem aStyle(FN_INSERT_PAGEHEADER, "Default Page Style");
+ SfxBoolItem aItem(FN_PARAM_1, true);
+ pViewShell->GetDispatcher()->ExecuteList(FN_INSERT_PAGEHEADER, SfxCallMode::API | SfxCallMode::SYNCHRON, {&aStyle, &aItem});
+ }
+ // Check Page Header State
+ checkPageHeaderOrFooter(pViewShell, FN_INSERT_PAGEHEADER, true);
+
+ // Remove Page Header
+ {
+ SfxStringItem aStyle(FN_INSERT_PAGEHEADER, "Default Page Style");
+ SfxBoolItem aItem(FN_PARAM_1, false);
+ pViewShell->GetDispatcher()->ExecuteList(FN_INSERT_PAGEHEADER, SfxCallMode::API | SfxCallMode::SYNCHRON, {&aStyle, &aItem});
+ }
+ // Check Page Header State
+ checkPageHeaderOrFooter(pViewShell, FN_INSERT_PAGEHEADER, false);
+}
+
+void SwTiledRenderingTest::testPageFooter()
+{
+ createDoc("dummy.fodt");
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ // Check Page Footer State
+ checkPageHeaderOrFooter(pViewShell, FN_INSERT_PAGEFOOTER, false);
+ // Insert Page Footer
+ {
+ SfxStringItem aPageStyle(FN_INSERT_PAGEFOOTER, "Default Page Style");
+ SfxBoolItem aItem(FN_PARAM_1, true);
+ pViewShell->GetDispatcher()->ExecuteList(FN_INSERT_PAGEFOOTER, SfxCallMode::API | SfxCallMode::SYNCHRON, {&aPageStyle, &aItem});
+ }
+ // Check Page Footer State
+ checkPageHeaderOrFooter(pViewShell, FN_INSERT_PAGEFOOTER, true);
+
+ // Remove Page Footer
+ {
+ SfxStringItem aPageStyle(FN_INSERT_PAGEFOOTER, "Default Page Style");
+ SfxBoolItem aItem(FN_PARAM_1, false);
+ pViewShell->GetDispatcher()->ExecuteList(FN_INSERT_PAGEFOOTER, SfxCallMode::API | SfxCallMode::SYNCHRON, {&aPageStyle, &aItem});
+ }
+ // Check Footer State
+ checkPageHeaderOrFooter(pViewShell, FN_INSERT_PAGEFOOTER, false);
+}
+
+void SwTiledRenderingTest::testTdf115088()
+{
+ // We have three lines in the test document and we try to copy the second and third line
+ // To the beginning of the document
+ SwXTextDocument* pXTextDocument = createDoc("tdf115088.odt");
+
+ // Select and copy second and third line
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_HOME | KEY_MOD1);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_HOME | KEY_MOD1);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN | KEY_SHIFT);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN | KEY_SHIFT);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT | KEY_SHIFT);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT | KEY_SHIFT);
+ Scheduler::ProcessEventsToIdle();
+ comphelper::dispatchCommand(".uno:Copy", uno::Sequence<beans::PropertyValue>());
+
+ // Move cursor to the beginning of the first line and paste
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_HOME | KEY_MOD1);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_HOME | KEY_MOD1);
+ Scheduler::ProcessEventsToIdle();
+ comphelper::dispatchCommand(".uno:PasteUnformatted", uno::Sequence<beans::PropertyValue>());
+ Scheduler::ProcessEventsToIdle();
+
+ // Check the resulting text in the document. (it was 1Text\n1\n1\n1)
+ CPPUNIT_ASSERT_EQUAL(OUString("1\n1Text\n1\n1"), pXTextDocument->getText()->getString());
+
+ mxComponent->dispose();
+ mxComponent.clear();
+ comphelper::LibreOfficeKit::setActive(false);
+}
+
+void SwTiledRenderingTest::testRedlineField()
+{
+ // Load a document.
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+
+ // Turn on track changes and type "x".
+ uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
+ xPropertySet->setPropertyValue("RecordChanges", uno::makeAny(true));
+
+ SwDateTimeField aDate(static_cast<SwDateTimeFieldType*>(pWrtShell->GetFieldType(0, SwFieldIds::DateTime)));
+ //aDate->SetDateTime(::DateTime(::DateTime::SYSTEM));
+ pWrtShell->Insert(aDate);
+
+ // Get the redline just created
+ const SwRedlineTable& rTable = pWrtShell->GetDoc()->getIDocumentRedlineAccess().GetRedlineTable();
+ CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(1), rTable.size());
+ SwRangeRedline* pRedline = rTable[0];
+ CPPUNIT_ASSERT(pRedline->GetDescr().indexOf(aDate.GetFieldName())!= -1);
+}
+
+void SwTiledRenderingTest::testIMESupport()
+{
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ VclPtr<vcl::Window> pDocWindow = pXTextDocument->getDocWindow();
+
+ SwView* pView = dynamic_cast<SwView*>(SfxViewShell::Current());
+ assert(pView);
+ SwWrtShell* pWrtShell = pView->GetWrtShellPtr();
+
+ // sequence of chinese IME compositions when 'nihao' is typed in an IME
+ const std::vector<OString> aUtf8Inputs{ "年", "你", "你好", "你哈", "你好", "你好" };
+ std::vector<OUString> aInputs;
+ std::transform(aUtf8Inputs.begin(), aUtf8Inputs.end(),
+ std::back_inserter(aInputs), [](OString aInput) {
+ return OUString::fromUtf8(aInput);
+ });
+ for (const auto& aInput: aInputs)
+ {
+ pDocWindow->PostExtTextInputEvent(VclEventId::ExtTextInput, aInput);
+ }
+ pDocWindow->PostExtTextInputEvent(VclEventId::EndExtTextInput, "");
+
+ // the cursor should be at position 2nd
+ SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), pShellCursor->GetPoint()->nContent.GetIndex());
+
+ // content contains only the last IME composition, not all
+ CPPUNIT_ASSERT_EQUAL(aInputs[aInputs.size() - 1].concat("Aaa bbb."), pShellCursor->GetPoint()->nNode.GetNode().GetTextNode()->GetText());
+}
+
+void SwTiledRenderingTest::testSplitNodeRedlineCallback()
+{
+ // Load a document.
+ SwXTextDocument* pXTextDocument = createDoc("splitnode_redline_callback.fodt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+
+ // 1. test case
+ // Move cursor between the two tracked changes
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
+ Scheduler::ProcessEventsToIdle();
+
+ // Add a new line
+ m_nRedlineTableEntryModified = 0;
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RETURN);
+ Scheduler::ProcessEventsToIdle();
+
+ // Assert that we get a notification about redline modification
+ // The redline after the inserted node gets a different vertical position
+ CPPUNIT_ASSERT_EQUAL(1, m_nRedlineTableEntryModified);
+
+ // 2. test case
+ // Move cursor back to the first line, so adding new line will affect both tracked changes
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_HOME | KEY_MOD1);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_HOME | KEY_MOD1);
+ Scheduler::ProcessEventsToIdle();
+
+ // Add a new line
+ m_nRedlineTableEntryModified = 0;
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RETURN);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(2, m_nRedlineTableEntryModified);
+
+ // 3. test case
+ // Move cursor to the end of the document, so adding a new line won't affect any tracked changes
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_END | KEY_MOD1);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_END | KEY_MOD1);
+ Scheduler::ProcessEventsToIdle();
+
+ // Add a new line
+ m_nRedlineTableEntryModified = 0;
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RETURN);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(0, m_nRedlineTableEntryModified);
+}
+
+void SwTiledRenderingTest::testDeleteNodeRedlineCallback()
+{
+ // Load a document.
+ SwXTextDocument* pXTextDocument = createDoc("removenode_redline_callback.fodt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+
+ // 1. test case
+ // Move cursor between the two tracked changes
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
+ Scheduler::ProcessEventsToIdle();
+
+ // Remove one (empty) line
+ m_nRedlineTableEntryModified = 0;
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DELETE);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DELETE);
+ Scheduler::ProcessEventsToIdle();
+
+ // Assert that we get a notification about redline modification
+ // The redline after the removed node gets a different vertical position
+ CPPUNIT_ASSERT_EQUAL(1, m_nRedlineTableEntryModified);
+
+ // 2. test case
+ // Move cursor back to the first line, so removing one line will affect both tracked changes
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_HOME | KEY_MOD1);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_HOME | KEY_MOD1);
+ Scheduler::ProcessEventsToIdle();
+
+ // Remove a new line
+ m_nRedlineTableEntryModified = 0;
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DELETE);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DELETE);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(2, m_nRedlineTableEntryModified);
+
+ // 3. test case
+ // Move cursor to the end of the document, so removing one line won't affect any tracked changes
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_END | KEY_MOD1);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_END | KEY_MOD1);
+ Scheduler::ProcessEventsToIdle();
+
+ // Remove a line
+ m_nRedlineTableEntryModified = 0;
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_BACKSPACE);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_BACKSPACE);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(0, m_nRedlineTableEntryModified);
+}
+
+
+void SwTiledRenderingTest::testVisCursorInvalidation()
+{
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ ViewCallback aView1(SfxViewShell::Current());
+ int nView1 = SfxLokHelper::getView();
+
+ SfxLokHelper::createView();
+ int nView2 = SfxLokHelper::getView();
+ ViewCallback aView2(SfxViewShell::Current());
+ Scheduler::ProcessEventsToIdle();
+
+
+ // Move visible cursor in the first view
+ SfxLokHelper::setView(nView1);
+ Scheduler::ProcessEventsToIdle();
+
+ aView1.m_bOwnCursorInvalidated = false;
+ aView1.m_bViewCursorInvalidated = false;
+ aView2.m_bOwnCursorInvalidated = false;
+ aView2.m_bViewCursorInvalidated = false;
+
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT);
+ Scheduler::ProcessEventsToIdle();
+
+ CPPUNIT_ASSERT(!aView1.m_bViewCursorInvalidated);
+ CPPUNIT_ASSERT(aView1.m_bOwnCursorInvalidated);
+ CPPUNIT_ASSERT(aView2.m_bViewCursorInvalidated);
+ CPPUNIT_ASSERT(!aView2.m_bOwnCursorInvalidated);
+
+ // Insert text in the second view which moves the other view's cursor too
+ SfxLokHelper::setView(nView2);
+
+ aView1.m_bOwnCursorInvalidated = false;
+ aView1.m_bViewCursorInvalidated = false;
+ aView2.m_bOwnCursorInvalidated = false;
+ aView2.m_bViewCursorInvalidated = false;
+
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ Scheduler::ProcessEventsToIdle();
+
+ CPPUNIT_ASSERT(aView1.m_bViewCursorInvalidated);
+ CPPUNIT_ASSERT(aView1.m_bOwnCursorInvalidated);
+ CPPUNIT_ASSERT(aView2.m_bViewCursorInvalidated);
+ CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated);
+
+ // Do the same as before, but set the related compatibility flag first
+ SfxLokHelper::setView(nView2);
+
+ comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(true);
+
+ aView1.m_bOwnCursorInvalidated = false;
+ aView1.m_bViewCursorInvalidated = false;
+ aView2.m_bOwnCursorInvalidated = false;
+ aView2.m_bViewCursorInvalidated = false;
+
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ Scheduler::ProcessEventsToIdle();
+
+ CPPUNIT_ASSERT(aView1.m_bViewCursorInvalidated);
+ CPPUNIT_ASSERT(aView1.m_bOwnCursorInvalidated);
+ CPPUNIT_ASSERT_EQUAL(nView2, aView1.m_nOwnCursorInvalidatedBy);
+ CPPUNIT_ASSERT(aView2.m_bViewCursorInvalidated);
+ CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated);
+ CPPUNIT_ASSERT_EQUAL(nView2, aView2.m_nOwnCursorInvalidatedBy);
+
+ comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(false);
+}
+
+void SwTiledRenderingTest::testDeselectCustomShape()
+{
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
+ Point aStart = pShellCursor->GetSttPos();
+ aStart.setX(aStart.getX() - 1000);
+ aStart.setY(aStart.getY() - 1000);
+
+ comphelper::dispatchCommand(".uno:BasicShapes.hexagon", uno::Sequence<beans::PropertyValue>());
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pWrtShell->GetDrawView()->GetMarkedObjectList().GetMarkCount());
+
+ pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aStart.getX(), aStart.getY(), 1, MOUSE_LEFT, 0);
+ pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, aStart.getX(), aStart.getY(), 1, MOUSE_LEFT, 0);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), pWrtShell->GetDrawView()->GetMarkedObjectList().GetMarkCount());
+}
+
+void SwTiledRenderingTest::testSemiTransparent()
+{
+ // Load a document where the top left tile contains a semi-transparent rectangle shape.
+ SwXTextDocument* pXTextDocument = createDoc("semi-transparent.odt");
+
+ // Render a larger area, and then get the color of the bottom right corner of our tile.
+ size_t nCanvasWidth = 1024;
+ size_t nCanvasHeight = 512;
+ size_t nTileSize = 256;
+ std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0);
+ ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::DEFAULT);
+ pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
+ pDevice->SetOutputSizePixelScaleOffsetAndBuffer(Size(nCanvasWidth, nCanvasHeight),
+ Fraction(1.0), Point(), aPixmap.data());
+ pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0,
+ /*nTilePosY=*/0, /*nTileWidth=*/15360, /*nTileHeight=*/7680);
+ pDevice->EnableMapMode(false);
+ Bitmap aBitmap = pDevice->GetBitmap(Point(0, 0), Size(nTileSize, nTileSize));
+ Bitmap::ScopedReadAccess pAccess(aBitmap);
+ Color aColor(pAccess->GetPixel(255, 255));
+
+ // Without the accompanying fix in place, this test would have failed with 'Expected greater or
+ // equal than: 190; Actual: 159'. This means the semi-transparent gray rectangle was darker than
+ // expected, as it was painted twice.
+ CPPUNIT_ASSERT_GREATEREQUAL(190, static_cast<int>(aColor.R));
+ CPPUNIT_ASSERT_GREATEREQUAL(190, static_cast<int>(aColor.G));
+ CPPUNIT_ASSERT_GREATEREQUAL(190, static_cast<int>(aColor.B));
+}
+
+void SwTiledRenderingTest::testHighlightNumbering()
+{
+ // Load a document where the top left tile contains a semi-transparent rectangle shape.
+ SwXTextDocument* pXTextDocument = createDoc("tdf114799.docx");
+
+ // Render a larger area, and then get the color of the bottom right corner of our tile.
+ size_t nCanvasWidth = 1024;
+ size_t nCanvasHeight = 512;
+ size_t nTileSize = 256;
+ std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0);
+ ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::DEFAULT);
+ pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
+ pDevice->SetOutputSizePixelScaleOffsetAndBuffer(Size(nCanvasWidth, nCanvasHeight),
+ Fraction(1.0), Point(), aPixmap.data());
+ pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0,
+ /*nTilePosY=*/0, /*nTileWidth=*/15360, /*nTileHeight=*/7680);
+ pDevice->EnableMapMode(false);
+ Bitmap aBitmap = pDevice->GetBitmap(Point(0, 0), Size(nTileSize, nTileSize));
+ Bitmap::ScopedReadAccess pAccess(aBitmap);
+
+ // Yellow highlighting over numbering
+ Color aColor(pAccess->GetPixel(103, 148));
+ CPPUNIT_ASSERT_EQUAL(COL_YELLOW, aColor);
+}
+
+void SwTiledRenderingTest::testClipText()
+{
+ // Load a document where the top left tile contains table text with
+ // too small line height, but with top and bottom paragraph margins,
+ // avoiding of clipping top and bottom parts of the characters.
+ SwXTextDocument* pXTextDocument = createDoc("tdf117448.fodt");
+
+ // Render a larger area, and then get the top and bottom of the text in that tile
+ size_t nCanvasWidth = 1024;
+ size_t nCanvasHeight = 512;
+ size_t nTileSize = 256;
+ std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0);
+ ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::DEFAULT);
+ pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
+ pDevice->SetOutputSizePixelScaleOffsetAndBuffer(Size(nCanvasWidth, nCanvasHeight),
+ Fraction(1.0), Point(), aPixmap.data());
+ pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0,
+ /*nTilePosY=*/0, /*nTileWidth=*/15360, /*nTileHeight=*/7680);
+ pDevice->EnableMapMode(false);
+ Bitmap aBitmap = pDevice->GetBitmap(Point(0, 0), Size(nTileSize, nTileSize));
+ Bitmap::ScopedReadAccess pAccess(aBitmap);
+
+ // check top margin, it's not white completely (i.e. showing top of letter "T")
+ bool bClipTop = true;
+ for (int i = 0; i < 150; i++)
+ {
+ Color aTopTextColor(pAccess->GetPixel(98, 98 + i));
+ if (aTopTextColor.R < 255)
+ {
+ bClipTop = false;
+ break;
+ }
+ }
+ CPPUNIT_ASSERT(!bClipTop);
+ // switch off because of false alarm on some platform, maybe related to font replacements
+#if 0
+ // check bottom margin, it's not white completely (i.e. showing bottom of letter "g")
+ bool bClipBottom = true;
+ for (int i = 0; i < 150; i++)
+ {
+ Color aBottomTextColor(pAccess->GetPixel(110, 98 + i));
+ if (aBottomTextColor.R < 255)
+ {
+ bClipBottom = false;
+ break;
+ }
+ }
+ CPPUNIT_ASSERT(!bClipBottom);
+#endif
+}
+
+void SwTiledRenderingTest::testAnchorTypes()
+{
+ SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
+ SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc();
+ SwView* pView = pXTextDocument->GetDocShell()->GetView();
+ pView->GetViewFrame()->GetDispatcher()->Execute(FN_CNTNT_TO_NEXT_FRAME, SfxCallMode::SYNCHRON);
+ SfxItemSet aSet(pDoc->GetAttrPool(), svl::Items<FN_TOOL_ANCHOR_PAGE, FN_TOOL_ANCHOR_PAGE>{});
+ SfxBoolItem aItem(FN_TOOL_ANCHOR_PAGE);
+ aSet.Put(aItem);
+ auto pShell = dynamic_cast<SwBaseShell*>(pView->GetCurShell());
+ pShell->GetState(aSet);
+ // Without the accompanying fix in place, this test would have failed, setting the anchor type
+ // to other than as/at-char was possible.
+ CPPUNIT_ASSERT(!aSet.HasItem(FN_TOOL_ANCHOR_PAGE));
+}
+
+void SwTiledRenderingTest::testLanguageStatus()
+{
+ SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
+ SwView* pView = pXTextDocument->GetDocShell()->GetView();
+ std::unique_ptr<SfxPoolItem> pItem;
+ pView->GetViewFrame()->GetBindings().QueryState(SID_LANGUAGE_STATUS, pItem);
+ auto pStringListItem = dynamic_cast<SfxStringListItem*>(pItem.get());
+ CPPUNIT_ASSERT(pStringListItem);
+
+ uno::Sequence< OUString > aList;
+ pStringListItem->GetStringList(aList);
+ CPPUNIT_ASSERT_EQUAL(OUString("English (USA);en-US"), aList[0]);
+}
+
+void SwTiledRenderingTest::testRedlineNotificationDuringSave()
+{
+ // Load a document with redlines which are hidden at a layout level.
+ // It's an empty document, just settings.xml and content.xml are custom.
+ SwXTextDocument* pXTextDocument = createDoc("redline-notification-during-save.odt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+
+ // Save the document.
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer8");
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ // Without the accompanying fix in place, this test would have never returned due to an infinite
+ // loop while sending not needed LOK notifications for redline changes during save.
+ xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+}
+
+void SwTiledRenderingTest::testHyperlink()
+{
+ comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(true);
+ SwXTextDocument* pXTextDocument = createDoc("hyperlink.odt");
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+ SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
+
+ Point aStart = pShellCursor->GetSttPos();
+ aStart.setX(aStart.getX() + 1800);
+ pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aStart.getX(), aStart.getY(), 1,
+ MOUSE_LEFT, 0);
+ pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, aStart.getX(), aStart.getY(), 1,
+ MOUSE_LEFT, 0);
+ Scheduler::ProcessEventsToIdle();
+
+ CPPUNIT_ASSERT_EQUAL(OString("hyperlink"), m_sHyperlinkText);
+ CPPUNIT_ASSERT_EQUAL(OString("http://example.com/"), m_sHyperlinkLink);
+}
+
+void SwTiledRenderingTest::testFieldmark()
+{
+ // Without the accompanying fix in place, this crashed on load.
+ createDoc("fieldmark.docx");
+}
+
+void SwTiledRenderingTest::testDropDownFormFieldButton()
+{
+ SwXTextDocument* pXTextDocument = createDoc("drop_down_form_field.odt");
+ pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000));
+
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+
+ // Move the cursor to trigger displaying of the field button.
+ pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false);
+ CPPUNIT_ASSERT(m_aFormFieldButton.isEmpty());
+
+ // Do a tile rendering to trigger the button message with a valid text area
+ size_t nCanvasWidth = 1024;
+ size_t nCanvasHeight = 512;
+ std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0);
+ ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::DEFAULT);
+ pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
+ pDevice->SetOutputSizePixelScaleOffsetAndBuffer(Size(nCanvasWidth, nCanvasHeight),
+ Fraction(1.0), Point(), aPixmap.data());
+ pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0,
+ /*nTilePosY=*/0, /*nTileWidth=*/10000, /*nTileHeight=*/4000);
+
+ CPPUNIT_ASSERT(!m_aFormFieldButton.isEmpty());
+ {
+ std::stringstream aStream(m_aFormFieldButton.getStr());
+ boost::property_tree::ptree aTree;
+ boost::property_tree::read_json(aStream, aTree);
+
+ OString sAction = aTree.get_child("action").get_value<std::string>().c_str();
+ CPPUNIT_ASSERT_EQUAL(OString("show"), sAction);
+
+ OString sType = aTree.get_child("type").get_value<std::string>().c_str();
+ CPPUNIT_ASSERT_EQUAL(OString("drop-down"), sType);
+
+ OString sTextArea = aTree.get_child("textArea").get_value<std::string>().c_str();
+ CPPUNIT_ASSERT_EQUAL(OString("1538, 1418, 1026, 275"), sTextArea);
+
+ boost::property_tree::ptree aItems = aTree.get_child("params").get_child("items");
+ CPPUNIT_ASSERT_EQUAL(size_t(6), aItems.size());
+
+ OStringBuffer aItemList;
+ for (auto &item : aItems)
+ {
+ aItemList.append(item.second.get_value<std::string>().c_str());
+ aItemList.append(";");
+ }
+ CPPUNIT_ASSERT_EQUAL(OString("2019/2020;2020/2021;2021/2022;2022/2023;2023/2024;2024/2025;"), aItemList.toString());
+
+ OString sSelected = aTree.get_child("params").get_child("selected").get_value<std::string>().c_str();
+ CPPUNIT_ASSERT_EQUAL(OString("1"), sSelected);
+
+ OString sPlaceholder = aTree.get_child("params").get_child("placeholderText").get_value<std::string>().c_str();
+ CPPUNIT_ASSERT_EQUAL(OString("No Item specified"), sPlaceholder);
+ }
+
+ // Move the cursor back so the button becomes hidden.
+ pWrtShell->Left(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false);
+
+ CPPUNIT_ASSERT(!m_aFormFieldButton.isEmpty());
+ {
+ std::stringstream aStream(m_aFormFieldButton.getStr());
+ boost::property_tree::ptree aTree;
+ boost::property_tree::read_json(aStream, aTree);
+
+ OString sAction = aTree.get_child("action").get_value<std::string>().c_str();
+ CPPUNIT_ASSERT_EQUAL(OString("hide"), sAction);
+
+ OString sType = aTree.get_child("type").get_value<std::string>().c_str();
+ CPPUNIT_ASSERT_EQUAL(OString("drop-down"), sType);
+ }
+}
+
+void SwTiledRenderingTest::testDropDownFormFieldButtonEditing()
+{
+ SwXTextDocument* pXTextDocument = createDoc("drop_down_form_field2.odt");
+ pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000));
+
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+
+ // Move the cursor to trigger displaying of the field button.
+ pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false);
+ CPPUNIT_ASSERT(m_aFormFieldButton.isEmpty());
+
+ // Do a tile rendering to trigger the button message with a valid text area
+ size_t nCanvasWidth = 1024;
+ size_t nCanvasHeight = 512;
+ std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0);
+ ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::DEFAULT);
+ pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
+ pDevice->SetOutputSizePixelScaleOffsetAndBuffer(Size(nCanvasWidth, nCanvasHeight),
+ Fraction(1.0), Point(), aPixmap.data());
+ pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0,
+ /*nTilePosY=*/0, /*nTileWidth=*/10000, /*nTileHeight=*/4000);
+
+ // The item with the index '1' is selected by default
+ CPPUNIT_ASSERT(!m_aFormFieldButton.isEmpty());
+ {
+ std::stringstream aStream(m_aFormFieldButton.getStr());
+ boost::property_tree::ptree aTree;
+ boost::property_tree::read_json(aStream, aTree);
+
+ OString sSelected = aTree.get_child("params").get_child("selected").get_value<std::string>().c_str();
+ CPPUNIT_ASSERT_EQUAL(OString("1"), sSelected);
+ }
+ m_aFormFieldButton = "";
+
+ // Trigger a form field event to select a different item.
+ vcl::ITiledRenderable::StringMap aArguments;
+ aArguments["type"] = "drop-down";
+ aArguments["cmd"] = "selected";
+ aArguments["data"] = "3";
+ pXTextDocument->executeFromFieldEvent(aArguments);
+
+ // Do a tile rendering to trigger the button message.
+ pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0,
+ /*nTilePosY=*/0, /*nTileWidth=*/10000, /*nTileHeight=*/4000);
+
+ CPPUNIT_ASSERT(!m_aFormFieldButton.isEmpty());
+ {
+ std::stringstream aStream(m_aFormFieldButton.getStr());
+ boost::property_tree::ptree aTree;
+ boost::property_tree::read_json(aStream, aTree);
+
+ OString sSelected = aTree.get_child("params").get_child("selected").get_value<std::string>().c_str();
+ CPPUNIT_ASSERT_EQUAL(OString("3"), sSelected);
+ }
+}
+
+void SwTiledRenderingTest::testDropDownFormFieldButtonNoSelection()
+{
+ SwXTextDocument* pXTextDocument = createDoc("drop_down_form_field_noselection.odt");
+ pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000));
+
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+
+ // Move the cursor to trigger displaying of the field button.
+ pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false);
+ CPPUNIT_ASSERT(m_aFormFieldButton.isEmpty());
+
+ // Do a tile rendering to trigger the button message with a valid text area
+ size_t nCanvasWidth = 1024;
+ size_t nCanvasHeight = 512;
+ std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0);
+ ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::DEFAULT);
+ pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
+ pDevice->SetOutputSizePixelScaleOffsetAndBuffer(Size(nCanvasWidth, nCanvasHeight),
+ Fraction(1.0), Point(), aPixmap.data());
+ pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0,
+ /*nTilePosY=*/0, /*nTileWidth=*/10000, /*nTileHeight=*/4000);
+
+ // None of the items is selected
+ CPPUNIT_ASSERT(!m_aFormFieldButton.isEmpty());
+ {
+ std::stringstream aStream(m_aFormFieldButton.getStr());
+ boost::property_tree::ptree aTree;
+ boost::property_tree::read_json(aStream, aTree);
+
+ OString sSelected = aTree.get_child("params").get_child("selected").get_value<std::string>().c_str();
+ CPPUNIT_ASSERT_EQUAL(OString("-1"), sSelected);
+ }
+}
+
+void SwTiledRenderingTest::testDropDownFormFieldButtonNoItem()
+{
+ SwXTextDocument* pXTextDocument = createDoc("drop_down_form_field_noitem.odt");
+ pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000));
+
+ SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+ pWrtShell->GetSfxViewShell()->registerLibreOfficeKitViewCallback(&SwTiledRenderingTest::callback, this);
+
+ // Move the cursor to trigger displaying of the field button.
+ pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false);
+ CPPUNIT_ASSERT(m_aFormFieldButton.isEmpty());
+
+ // Do a tile rendering to trigger the button message with a valid text area
+ size_t nCanvasWidth = 1024;
+ size_t nCanvasHeight = 512;
+ std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0);
+ ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::DEFAULT);
+ pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
+ pDevice->SetOutputSizePixelScaleOffsetAndBuffer(Size(nCanvasWidth, nCanvasHeight),
+ Fraction(1.0), Point(), aPixmap.data());
+ pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0,
+ /*nTilePosY=*/0, /*nTileWidth=*/10000, /*nTileHeight=*/4000);
+
+ // There is not item specified for the field
+ CPPUNIT_ASSERT(!m_aFormFieldButton.isEmpty());
+ {
+ std::stringstream aStream(m_aFormFieldButton.getStr());
+ boost::property_tree::ptree aTree;
+ boost::property_tree::read_json(aStream, aTree);
+
+ boost::property_tree::ptree aItems = aTree.get_child("params").get_child("items");
+ CPPUNIT_ASSERT_EQUAL(size_t(0), aItems.size());
+
+ OString sSelected = aTree.get_child("params").get_child("selected").get_value<std::string>().c_str();
+ CPPUNIT_ASSERT_EQUAL(OString("-1"), sSelected);
+ }
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(SwTiledRenderingTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */