Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/LibreOffice/sw/qa/extras/tiledrendering/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 174 kB image not shown  

Quelle  tiledrendering.cxx   Sprache: C

 
/* -*- 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 <swtiledrenderingtest.hxx>

#include <string>
#include <string_view>

#include <boost/property_tree/json_parser.hpp>

#include <com/sun/star/frame/XStorable.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/text/XTextViewCursorSupplier.hpp>
#include <com/sun/star/text/XTextField.hpp>
#include <com/sun/star/text/AuthorDisplayFormat.hpp>
#include <com/sun/star/datatransfer/XTransferable2.hpp>
#include <com/sun/star/frame/XDispatchResultListener.hpp>
#include <com/sun/star/frame/DispatchResultState.hpp>

#include <test/helper/transferable.hxx>
#include <comphelper/dispatchcommand.hxx>
#include <comphelper/propertysequence.hxx>
#include <comphelper/scopeguard.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdview.hxx>
#include <vcl/virdev.hxx>
#include <vcl/filter/PngImageWriter.hxx>
#include <editeng/editview.hxx>
#include <editeng/outliner.hxx>
#include <editeng/wghtitem.hxx>
#include <svl/srchitem.hxx>
#include <svl/slstitm.hxx>
#include <svl/stritem.hxx>
#include <svl/voiditem.hxx>
#include <sfx2/viewsh.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/viewfrm.hxx>
#include <vcl/scheduler.hxx>
#include <vcl/vclevent.hxx>
#include <vcl/BitmapReadAccess.hxx>
#include <vcl/ITiledRenderable.hxx>
#include <vcl/themecolors.hxx>
#include <tools/json_writer.hxx>
#include <unotools/mediadescriptor.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/propertyvalue.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <sfx2/lokhelper.hxx>
#include <comphelper/lok.hxx>
#include <comphelper/string.hxx>
#include <unotools/searchopt.hxx>

#include <drawdoc.hxx>
#include <ndtxt.hxx>
#include <view.hxx>
#include <UndoManager.hxx>
#include <cmdid.h>
#include <redline.hxx>
#include <IDocumentDrawModelAccess.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <flddat.hxx>
#include <basesh.hxx>
#include <txtfrm.hxx>
#include <rootfrm.hxx>
#include <fmtanchr.hxx>
#include <docsh.hxx>
#include <wrtsh.hxx>
#include <unotxdoc.hxx>
#include <textcontentcontrol.hxx>
#include <swtestviewcallback.hxx>

static std::ostream& operator<<(std::ostream& os, ViewShellId id)
{
    os << static_cast<sal_Int32>(id);
    return os;
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testRegisterCallback)
{
    createDoc("dummy.fodt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
    // Insert a character at the beginning of the document.
    pWrtShell->Insert(u"x"_ustr);
    Scheduler::ProcessEventsToIdle();

    // 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.Overlaps(aTopLeft));
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testPostKeyEvent)
{
    createDoc("dummy.fodt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Right(SwCursorSkipMode::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()->GetContentIndex());

    emulateTyping(u"x");
    // Did we manage to insert the character after the first one?
    CPPUNIT_ASSERT_EQUAL(u"Axaa bbb."_ustr, pShellCursor->GetPoint()->GetNode().GetTextNode()->GetText());
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testPostMouseEvent)
{
    SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Right(SwCursorSkipMode::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()->GetContentIndex());

    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()->GetContentIndex());
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSetTextSelection)
{
    SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    // Move the cursor into the second word.
    pWrtShell->Right(SwCursorSkipMode::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(u"bbb"_ustr, 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(u"Aaa bbb"_ustr, 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(u"Aaa b"_ustr, pShellCursor->GetText());
}

CPPUNIT_TEST_FIXTURE(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"_ostr));

    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    // Move the cursor into the first word.
    pWrtShell->Right(SwCursorSkipMode::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("Hello"_ostr, apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/plain;charset=utf-8"_ostr));

    // Make sure we produce something for HTML.
    CPPUNIT_ASSERT(!apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/html"_ostr).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("Shape"_ostr, apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/plain;charset=utf-8"_ostr));
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testGetTextSelectionLineLimit)
{
    static OStringLiteral sOriginalText(u8"Estonian employs the Latin script as the basis for its alphabet, which adds the letters ä, ö, ü, and õ, plus the later additions š and ž. The letters c, q, w, x and y are limited to proper names of foreign origin, and f, z, š, and ž appear in loanwords and foreign names only. Ö and Ü are pronounced similarly to their equivalents in Swedish and German. Unlike in standard German but like Swedish (when followed by 'r') and Finnish, Ä is pronounced [æ], as in English mat. The vowels Ä, Ö and Ü are clearly separate phonemes and inherent in Estonian, although the letter shapes come from German. The letter õ denotes /ɤ/, unrounded /o/, or a close-mid back unrounded vowel. It is almost identical to the Bulgarian ъ /ɤ̞/ and the Vietnamese ơ, and is also used to transcribe the Russian ы.");
    static OStringLiteral sExpectedHtml(u8"Estonian employs the https://en.wikipedia.org/wiki/Latin_script\">Latin script</a> as the basis for <a href=\"https://en.wikipedia.org/wiki/Estonian_alphabet\">its alphabet</a>, which adds the letters <a href=\"https://en.wikipedia.org/wiki/%C3%84\"><i>ä</i></a>, <a href=\"https://en.wikipedia.org/wiki/%C3%96\"><i>ö</i></a>, <a href=\"https://en.wikipedia.org/wiki/%C3%9C\"><i>ü</i></a>, and <a href=\"https://en.wikipedia.org/wiki/%C3%95\"><i>õ</i></a>, plus the later additions <a href=\"https://en.wikipedia.org/wiki/%C5%A0\"><i>š</i></a> and <a href=\"https://en.wikipedia.org/wiki/%C5%BD\"><i>ž</i></a>. The letters <i>c</i>, <i>q</i>, <i>w</i>, <i>x</i> and <i>y</i> are limited to <a href=\"https://en.wikipedia.org/wiki/Proper_names\">proper names</a> of foreign origin, and <i>f</i>, <i>z</i>, <i>š</i>, and <i>ž</i> appear in loanwords and foreign names only. <i>Ö</i> and <i>Ü</i> are pronounced similarly to their equivalents in Swedish and German. Unlike in standard German but like Swedish (when followed by 'r') and Finnish, <i>Ä</i> is pronounced [æ], as in English <i>mat</i>. The vowels Ä, Ö and Ü are clearly separate <a href=\"https://en.wikipedia.org/wiki/Phonemes\">phonemes</a> and inherent in Estonian, although the letter shapes come from German. The letter <a href=\"https://en.wikipedia.org/wiki/%C3%95\"><i>õ</i></a> denotes /ɤ/, unrounded /o/, or a <a href=\"https://en.wikipedia.org/wiki/Close-mid_back_unrounded_vowel\">close-mid back unrounded vowel</a>. It is almost identical to the <a href=\"https://en.wikipedia.org/wiki/Bulgarian_language\">Bulgarian</a> <a href=\"https://en.wikipedia.org/wiki/%D0%AA\">ъ</a> /ɤ̞/ and the <a href=\"https://en.wikipedia.org/wiki/Vietnamese_language\">Vietnamese</a> <a href=\"https://en.wikipedia.org/wiki/%C6%A0\">ơ</a>, and is also used to transcribe the Russian <a href=\"https://en.wikipedia.org/wiki/%D0%AB\">ы</a>.");

    SwXTextDocument* pXTextDocument = createDoc("estonian.odt");

    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    // Move the cursor into the first word.
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 2, /*bBasicCall=*/false);
    // Create a selection.
    pWrtShell->SelAll();

    OString sPlainText = apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/plain;charset=utf-8"_ostr);

    CPPUNIT_ASSERT_EQUAL(OString(sOriginalText), sPlainText.trim());

    OString sHtmlText = apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/html"_ostr);

    int nStart = sHtmlText.indexOf(u8"Estonian");

    CPPUNIT_ASSERT(sHtmlText.match(sExpectedHtml, nStart));
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testGetTextSelectionMultiLine)
{
    // Test will check if correct number of new line marks / paragraphs is generated
    static OStringLiteral sOriginalText(u8"Heading\n\
Let's have text; we need to be able to select the text inside the shape, but also the various individual ones too:\n\
\n\
\n\
\n\
\n\
\n\
And this is all for Writer shape objects\n\
Heading on second page");

    static OStringLiteral sExpectedHtml(u8"Heading\n\
<p>Let's have text; we need to be able to select the text inside the shape, but also the various individual ones too:

\n\

<p><br/><br/></p>\n\
<p><br/><br/></p>\n\
<p><br/><br/></p>\n\
<p><br/><br/></p>\n\
<p><br/><br/></p>\n\
<h1 class=\"western\">And this is all for Writer shape objects</h1>\n\
<h2 class=\"western\">Heading on second page</h2>");

    SwXTextDocument* pXTextDocument = createDoc("multiline.odt");

    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    // Create a selection.
    pWrtShell->SelAll();

    OString sPlainText = apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/plain;charset=utf-8"_ostr);

    CPPUNIT_ASSERT_EQUAL(OString(sOriginalText), sPlainText.trim());

    OString sHtmlText = apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/html"_ostr);

    int nStart = sHtmlText.indexOf(u8"Heading");

    CPPUNIT_ASSERT(sHtmlText.match(sExpectedHtml, nStart));
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSetGraphicSelection)
{
    SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
    SwWrtShell* pWrtShell = getSwDocShell()->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.getOpenWidth(), aShapeAfter.getOpenWidth());
    CPPUNIT_ASSERT_EQUAL(aShapeBefore.getOpenHeight() + 1000, aShapeAfter.getOpenHeight());
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testResetSelection)
{
    SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    // Select one character.
    pWrtShell->Right(SwCursorSkipMode::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());
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testInsertShape)
{
    SwXTextDocument* pXTextDocument = createDoc("2-pages.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000));
    comphelper::dispatchCommand(u".uno:BasicShapes.circle"_ustr, 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(Point(3299, 299), Size(3403, 3403)), 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::Any(u"shape"_ustr)},
                {"SearchItem.Backward", uno::Any(bBackward)}
                }));
    comphelper::dispatchCommand(u".uno:ExecuteSearch"_ustr, aPropertyValues);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSearch)
{
    createDoc("search.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
    SwNodeOffset nNode = pWrtShell->getShellCursor(false)->Start()->GetNode().GetIndex();

    // First hit, in the second paragraph, before the shape.
    lcl_search(false);
    CPPUNIT_ASSERT(!pWrtShell->GetDrawView()->GetTextEditObject());
    SwNodeOffset nActual = pWrtShell->getShellCursor(false)->Start()->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()->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()->GetNode().GetIndex();
    CPPUNIT_ASSERT_EQUAL(nNode + 1, nActual);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSearchViewArea)
{
    createDoc("search.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->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::Any(u"Heading"_ustr)},
                {"SearchItem.Backward", uno::Any(false)},
                {"SearchItem.SearchStartPointX", uno::Any(static_cast<sal_Int32>(aPoint.getX()))},
                {"SearchItem.SearchStartPointY", uno::Any(static_cast<sal_Int32>(aPoint.getY()))}
                }));
    comphelper::dispatchCommand(u".uno:ExecuteSearch"_ustr, aPropertyValues);
    // This was just "Heading", i.e. SwView::SearchAndWrap() did not search from only the top of the second page.
    CPPUNIT_ASSERT_EQUAL(u"Heading on second page"_ustr, pShellCursor->GetPoint()->GetNode().GetTextNode()->GetText());
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSearchTextFrame)
{
    createDoc("search.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
    uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
                {
                {"SearchItem.SearchString", uno::Any(u"TextFrame"_ustr)},
                {"SearchItem.Backward", uno::Any(false)},
                }));
    comphelper::dispatchCommand(u".uno:ExecuteSearch"_ustr, aPropertyValues);
    // This was empty: nothing was highlighted after searching for 'TextFrame'.
    CPPUNIT_ASSERT(!m_aTextSelection.isEmpty());
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSearchTextFrameWrapAround)
{
    createDoc("search.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
    uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
                {
                {"SearchItem.SearchString", uno::Any(u"TextFrame"_ustr)},
                {"SearchItem.Backward", uno::Any(false)},
                }));
    comphelper::dispatchCommand(u".uno:ExecuteSearch"_ustr, aPropertyValues);
    CPPUNIT_ASSERT(m_bFound);
    comphelper::dispatchCommand(u".uno:ExecuteSearch"_ustr, aPropertyValues);
    // This failed, i.e. the second time 'not found' was reported, instead of wrapping around.
    CPPUNIT_ASSERT(m_bFound);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testDocumentSizeChanged)
{
    // Get the current document size.
    SwXTextDocument* pXTextDocument = createDoc("2-pages.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
    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());
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSearchAll)
{
    createDoc("search.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
    uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
                {
                {"SearchItem.SearchString", uno::Any(u"shape"_ustr)},
                {"SearchItem.Backward", uno::Any(false)},
                {"SearchItem.Command", uno::Any(static_cast<sal_uInt16>(SvxSearchCmd::FIND_ALL))},
                }));
    comphelper::dispatchCommand(u".uno:ExecuteSearch"_ustr, 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]);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSearchAllNotifications)
{
    createDoc("search.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
    // Reset notification counter before search.
    m_nSelectionBeforeSearchResult = 0;
    uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
                {
                {"SearchItem.SearchString", uno::Any(u"shape"_ustr)},
                {"SearchItem.Backward", uno::Any(false)},
                {"SearchItem.Command", uno::Any(static_cast<sal_uInt16>(SvxSearchCmd::FIND_ALL))},
                }));
    comphelper::dispatchCommand(u".uno:ExecuteSearch"_ustr, 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);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testPageDownInvalidation)
{
    SwXTextDocument* pXTextDocument = createDoc("pagedown-invalidation.odt");
    uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
                {
                {".uno:HideWhitespace", uno::Any(true)},
                }));
    pXTextDocument->initializeForTiledRendering(aPropertyValues);
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
    comphelper::dispatchCommand(u".uno:PageDown"_ustr, uno::Sequence<beans::PropertyValue>());

    // This was 2.
    CPPUNIT_ASSERT_EQUAL(0, m_nInvalidations);
}

CPPUNIT_TEST_FIXTURE(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());
    }
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testMissingInvalidation)
{
    // Create two views.
    SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
    SwTestViewCallback aView1;
    int nView1 = SfxLokHelper::getView();
    SfxLokHelper::createView();
    SwTestViewCallback aView2;
    int nView2 = SfxLokHelper::getView();

    // First view: put the cursor into the first word.
    SfxLokHelper::setView(nView1);
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);

    // Second view: select the first word.
    SfxLokHelper::setView(nView2);
    CPPUNIT_ASSERT(getSwDocShell()->GetWrtShell() != pWrtShell);
    pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
    pWrtShell->SelWrd();

    // Now delete the selected word and make sure both views are invalidated.
    Scheduler::ProcessEventsToIdle();
    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);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testViewCursors)
{
    createDoc("dummy.fodt");
    SwTestViewCallback aView1;
    SfxLokHelper::createView();
    SwTestViewCallback aView2;

    Scheduler::ProcessEventsToIdle();
    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 = getSwDocShell()->GetWrtShell();
    // Move the cursor into the second word.
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 5, /*bBasicCall=*/false);
    // Create a selection on the word.
    pWrtShell->SelWrd();
    Scheduler::ProcessEventsToIdle();
    SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
    // Did we indeed manage to select the second word?
    CPPUNIT_ASSERT_EQUAL(u"bbb"_ustr, 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);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testShapeViewCursors)
{
    // Load a document and create a view, so we have 2 ones.
    SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
    SwTestViewCallback aView1;
    SfxLokHelper::createView();
    pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
    SwTestViewCallback aView2;
    SwWrtShell* pWrtShell2 = getSwDocShell()->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());
    emulateTyping(u"x");
    // 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;

    emulateTyping(u"y");
    // 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);
    CPPUNIT_ASSERT(aLastViewCursor1 != aView1.m_aViewCursor);
    CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated);
    CPPUNIT_ASSERT(aLastOwnCursor2 != aView2.m_aOwnCursor);
    CPPUNIT_ASSERT_EQUAL(aLastViewCursor2, aView2.m_aViewCursor);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testViewCursorVisibility)
{
    // Load a document that has a shape and create two views.
    SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
    SwTestViewCallback aView1;
    SfxLokHelper::createView();
    pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
    SwTestViewCallback aView2;
    // 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 = getSwDocShell()->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);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testViewCursorCleanup)
{
    // Load a document that has a shape and create two views.
    SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
    SwTestViewCallback aView1;
    int nView2 = SfxLokHelper::createView();
    pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
    {
        SwTestViewCallback aView2;

        // Click on the shape in the second view.
        SwWrtShell* pWrtShell = getSwDocShell()->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(0));
    // Make sure that the graphic view selection on the first view is cleaned up.
    CPPUNIT_ASSERT(!aView1.m_bGraphicViewSelection);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testViewLock)
{
    // Load a document that has a shape and create two views.
    SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
    SwTestViewCallback aView1;
    SfxLokHelper::createView();
    pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
    SwTestViewCallback aView2;

    // Begin text edit in the second view and assert that the first gets a lock
    // notification.
    SwWrtShell* pWrtShell = getSwDocShell()->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);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTextEditViewInvalidations)
{
    // Load a document that has a shape and create two views.
    SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
    SwTestViewCallback aView1;
    SfxLokHelper::createView();
    pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
    SwTestViewCallback aView2;

    // Begin text edit in the second view.
    SwWrtShell* pWrtShell = getSwDocShell()->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());
    emulateTyping(u"x");

    // Assert that both views are invalidated when pressing a key while in text edit.
    aView1.m_bTilesInvalidated = false;
    emulateTyping(u"y");

    CPPUNIT_ASSERT(aView1.m_bTilesInvalidated);

    pWrtShell->EndTextEdit();
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoInvalidations)
{
    // Load a document and create two views.
    SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
    SwTestViewCallback aView1;
    int nView1 = SfxLokHelper::getView();
    SfxLokHelper::createView();
    pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
    SwTestViewCallback aView2;
    SfxLokHelper::setView(nView1);

    // Insert a character the end of the document.
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->EndOfSection();
    emulateTyping(u"c");
    // ProcessEventsToIdle resets the view; set it again
    SfxLokHelper::setView(nView1);
    SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
    CPPUNIT_ASSERT_EQUAL(u"Aaa bbb.c"_ustr, pShellCursor->GetPoint()->GetNode().GetTextNode()->GetText());

    // Undo and assert that both views are invalidated.
    Scheduler::ProcessEventsToIdle();
    aView1.m_bTilesInvalidated = false;
    aView2.m_bTilesInvalidated = false;
    comphelper::dispatchCommand(u".uno:Undo"_ustr, {});
    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);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoLimiting)
{
    // Load a document and create two views.
    SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
    SwWrtShell* pWrtShell1 = getSwDocShell()->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 = getSwDocShell()->GetWrtShell();
    pWrtShell2->EndOfSection();
    emulateTyping(u"c");
    SwShellCursor* pShellCursor = pWrtShell2->getShellCursor(false);
    CPPUNIT_ASSERT_EQUAL(u"Aaa bbb.c"_ustr, pShellCursor->GetPoint()->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()->setLibreOfficeKitViewCallback(nullptr);
    SfxLokHelper::setView(nView2);
    SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoReordering)
{
    // Create two views and a document of 2 paragraphs.
    SwXTextDocument* pXTextDocument = createDoc();
    SwWrtShell* pWrtShell1 = getSwDocShell()->GetWrtShell();
    int nView1 = SfxLokHelper::getView();
    int nView2 = SfxLokHelper::createView();
    pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
    SwWrtShell* pWrtShell2 = getSwDocShell()->GetWrtShell();
    pWrtShell2->SplitNode();
    SfxLokHelper::setView(nView1);
    pWrtShell1->SttEndDoc(/*bStt=*/true);
    SwTextNode* pTextNode1 = pWrtShell1->GetCursor()->GetPointNode().GetTextNode();
    // View 1 types into the first paragraph.
    emulateTyping(u"a");
    SfxLokHelper::setView(nView2);
    pWrtShell2->SttEndDoc(/*bStt=*/false);
    SwTextNode* pTextNode2 = pWrtShell2->GetCursor()->GetPointNode().GetTextNode();
    // View 2 types into the second paragraph.
    emulateTyping(u"z");
    CPPUNIT_ASSERT_EQUAL(u"a"_ustr, pTextNode1->GetText());
    CPPUNIT_ASSERT_EQUAL(u"z"_ustr, pTextNode2->GetText());

    // When view 1 presses undo:
    SfxLokHelper::setView(nView1);
    dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});

    // Then make sure view 1's last undo action is invoked, out of order:
    // Without the accompanying fix in place, this test would have failed with:
    // - Expression: pTextNode1->GetText().isEmpty()
    // i.e. the "a" in the first paragraph was not removed.
    CPPUNIT_ASSERT(pTextNode1->GetText().isEmpty());
    // Last undo action is not invoked, as it belongs to view 2.
    CPPUNIT_ASSERT_EQUAL(u"z"_ustr, pTextNode2->GetText());
    SfxLokHelper::setView(nView1);
    SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
    SfxLokHelper::setView(nView2);
    SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoReorderingRedo)
{
    // Create two views and a document of 2 paragraphs.
    SwXTextDocument* pXTextDocument = createDoc();
    SwWrtShell* pWrtShell1 = getSwDocShell()->GetWrtShell();
    int nView1 = SfxLokHelper::getView();
    int nView2 = SfxLokHelper::createView();
    pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
    SwWrtShell* pWrtShell2 = getSwDocShell()->GetWrtShell();
    pWrtShell2->SplitNode();
    SfxLokHelper::setView(nView1);
    pWrtShell1->SttEndDoc(/*bStt=*/true);
    SwTextNode* pTextNode1 = pWrtShell1->GetCursor()->GetPointNode().GetTextNode();
    // View 1 types into the first paragraph, twice.
    emulateTyping(u"f");
    // Go to the start of the paragraph, to avoid grouping.
    pWrtShell1->SttEndDoc(/*bStt=*/true);
    emulateTyping(u"s");
    SfxLokHelper::setView(nView2);
    pWrtShell2->SttEndDoc(/*bStt=*/false);
    SwTextNode* pTextNode2 = pWrtShell2->GetCursor()->GetPointNode().GetTextNode();
    // View 2 types into the second paragraph.
    emulateTyping(u"z");
    CPPUNIT_ASSERT_EQUAL(u"sf"_ustr, pTextNode1->GetText());
    CPPUNIT_ASSERT_EQUAL(u"z"_ustr, pTextNode2->GetText());

    // When view 1 presses undo, twice:
    SfxLokHelper::setView(nView1);
    dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
    // First just s(econd) is erased:
    CPPUNIT_ASSERT_EQUAL(u"f"_ustr, pTextNode1->GetText());
    dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});

    // Then make sure view 1's undo actions are invoked, out of order:
    // Without the accompanying fix in place, this test would have failed with:
    // - Expression: pTextNode1->GetText().isEmpty()
    // i.e. out of order undo was executed only once, not twice.
    CPPUNIT_ASSERT(pTextNode1->GetText().isEmpty());
    // The top undo action is not invoked, as it belongs to view 2.
    CPPUNIT_ASSERT_EQUAL(u"z"_ustr, pTextNode2->GetText());
    SfxLokHelper::setView(nView1);
    SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
    SfxLokHelper::setView(nView2);
    SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoReorderingRedo2)
{
    // Create two views.
    SwXTextDocument* pXTextDocument = createDoc();
    SwWrtShell* pWrtShell1 = getSwDocShell()->GetWrtShell();
    int nView1 = SfxLokHelper::getView();
    int nView2 = SfxLokHelper::createView();
    pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
    SwWrtShell* pWrtShell2 = getSwDocShell()->GetWrtShell();

    // Type in the first view.
    SfxLokHelper::setView(nView1);
    pWrtShell1->SttEndDoc(/*bStt=*/true);
    pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'f', 0);
    pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'f', 0);
    Scheduler::ProcessEventsToIdle();

    // Type to the same paragraph in the second view.
    SfxLokHelper::setView(nView2);
    pWrtShell2->SttEndDoc(/*bStt=*/true);
    pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 's', 0);
    pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 's', 0);
    Scheduler::ProcessEventsToIdle();

    // Delete in the first view and undo.
    SfxLokHelper::setView(nView1);
    pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::BACKSPACE);
    pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::BACKSPACE);
    Scheduler::ProcessEventsToIdle();
    dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
    Scheduler::ProcessEventsToIdle();

    // Query the undo state, now that a "delete" is on the redo stack and an "insert" belongs to the
    // view on the undo stack, so the types are different.
    SwUndoId nUndoId(SwUndoId::EMPTY);
    // Without the accompanying fix in place, this test would have failed with:
    // runtime error: downcast which does not point to an object of type 'const SwUndoInsert'
    // note: object is of type 'SwUndoDelete'
    // in an UBSan build.
    pWrtShell1->GetLastUndoInfo(nullptr, &nUndoId, &pWrtShell1->GetView());
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoReorderingMulti)
{
    // Create two views and a document of 2 paragraphs.
    SwXTextDocument* pXTextDocument = createDoc();
    SwWrtShell* pWrtShell1 = getSwDocShell()->GetWrtShell();
    int nView1 = SfxLokHelper::getView();
    int nView2 = SfxLokHelper::createView();
    pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
    SwWrtShell* pWrtShell2 = getSwDocShell()->GetWrtShell();
    pWrtShell2->SplitNode();
    SfxLokHelper::setView(nView1);
    pWrtShell1->SttEndDoc(/*bStt=*/true);
    SwTextNode* pTextNode1 = pWrtShell1->GetCursor()->GetPointNode().GetTextNode();
    // View 1 types into the first paragraph.
    emulateTyping(u"a");
    SfxLokHelper::setView(nView2);
    pWrtShell2->SttEndDoc(/*bStt=*/false);
    SwTextNode* pTextNode2 = pWrtShell2->GetCursor()->GetPointNode().GetTextNode();
    // View 2 types into the second paragraph, twice.
    emulateTyping(u"x");
    // Go to the start of the paragraph, to avoid grouping.
    pWrtShell2->SttPara();
    emulateTyping(u"y");
    CPPUNIT_ASSERT_EQUAL(u"a"_ustr, pTextNode1->GetText());
    CPPUNIT_ASSERT_EQUAL(u"yx"_ustr, pTextNode2->GetText());

    // When view 1 presses undo:
    SfxLokHelper::setView(nView1);
    dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});

    // Then make sure view 1's undo action is invoked, out of order:
    // Without the accompanying fix in place, this test would have failed with:
    // - Expression: pTextNode1->GetText().isEmpty()
    // i.e. out of order undo was not executed, the first paragraph was still "a".
    CPPUNIT_ASSERT(pTextNode1->GetText().isEmpty());
    // The top 2 undo actions are not invoked, as they belong to view 2.
    CPPUNIT_ASSERT_EQUAL(u"yx"_ustr, pTextNode2->GetText());
    SfxLokHelper::setView(nView1);
    SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
    SfxLokHelper::setView(nView2);
    SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoShapeLimiting)
{
    // Load a document and create a view.
    SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
    SwWrtShell* pWrtShell1 = getSwDocShell()->GetWrtShell();
    int nView1 = SfxLokHelper::getView();
    int nView2 = SfxLokHelper::createView();
    pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
    SwWrtShell* pWrtShell2 = getSwDocShell()->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());
    emulateTyping(u"x");
    pWrtShell2->EndTextEdit();

    // Assert that the first view can't and the second view can undo the insertion.
    SwDoc* pDoc = getSwDocShell()->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());

    rUndoManager.SetView(nullptr);

    SfxLokHelper::setView(nView1);
    SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
    SfxLokHelper::setView(nView2);
    SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
}

CPPUNIT_TEST_FIXTURE(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);
    emulateTyping(u"c");

    // Click before the first word in the second view.
    SfxLokHelper::setView(nView2);
    SwWrtShell* pWrtShell = getSwDocShell()->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()->setLibreOfficeKitViewCallback(nullptr);
    SfxLokHelper::setView(nView2);
    SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
}

CPPUNIT_TEST_FIXTURE(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);
    emulateTyping(u"c");

    // Assert that by default the second view can't undo the action.
    SfxLokHelper::setView(nView2);
    SwDoc* pDoc = getSwDocShell()->GetDoc();
    sw::UndoManager& rUndoManager = pDoc->GetUndoManager();
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rUndoManager.GetUndoActionCount());
    comphelper::dispatchCommand(u".uno:Undo"_ustr, {});
    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::Any(true)}
                }));
    comphelper::dispatchCommand(u".uno:Undo"_ustr, 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()->setLibreOfficeKitViewCallback(nullptr);
    SfxLokHelper::setView(nView2);
    SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testShapeTextUndoShells)
{
    // Load a document and create a view.
    createDoc("shape.fodt");
    sal_Int32 nView1 = SfxLokHelper::getView();

    // Begin text edit.
    SwWrtShell* pWrtShell = getSwDocShell()->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());
    emulateTyping(u"x");
    pWrtShell->EndTextEdit();

    // Make sure that the undo item remembers who created it.
    SwDoc* pDoc = getSwDocShell()->GetDoc();
    sw::UndoManager& rUndoManager = pDoc->GetUndoManager();
    CPPUNIT_ASSERT_EQUAL(size_t(1), rUndoManager.GetUndoActionCount());
    CPPUNIT_ASSERT_EQUAL(u"Edit text of Shape 'Shape1'"_ustr, rUndoManager.GetUndoActionComment(0));

    // This was -1: the view shell id for the undo action wasn't known.
    CPPUNIT_ASSERT_EQUAL(ViewShellId(nView1), rUndoManager.GetUndoAction()->GetViewShellId());
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testShapeTextUndoGroupShells)
{
    // Load a document and create a view.
    SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
    SwTestViewCallback aView1;
    sal_Int32 nView1 = SfxLokHelper::getView();

    // Begin text edit.
    SwWrtShell* pWrtShell = getSwDocShell()->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());
    emulateTyping(u"x");
    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 = getSwDocShell()->GetDoc();
    sw::UndoManager& rUndoManager = pDoc->GetUndoManager();
    CPPUNIT_ASSERT_EQUAL(size_t(0), rUndoManager.GetUndoActionCount());

    pWrtShell->EndTextEdit();
    pWrtShell->GetView().BeginTextEdit(pObject, pView->GetSdrPageView(), pWrtShell->GetWin());

    CPPUNIT_ASSERT_EQUAL(size_t(1), rUndoManager.GetUndoActionCount());
    CPPUNIT_ASSERT_EQUAL(u"Edit text of Shape 'Shape1'"_ustr, rUndoManager.GetUndoActionComment(0));

    // 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();
    emulateTyping(u"x");
    // 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({});
    SwTestViewCallback aView2;
    // 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);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTrackChanges)
{
    // Load a document.
    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(u"RecordChanges"_ustr, uno::Any(true));
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    SwTestViewCallback aView(pWrtShell->GetSfxViewShell());
    pWrtShell->EndOfSection();
    pWrtShell->Insert(u"zzz"_ustr);
    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::Any(o3tl::narrowing<sal_uInt16>(pRedline->GetId()))}
                }));
    comphelper::dispatchCommand(u".uno:RejectTrackedChange"_ustr, 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(u"Aaa bbb."_ustr, pShellCursor->GetPoint()->GetNode().GetTextNode()->GetText());
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTrackChangesCallback)
{
    // Load a document.
    createDoc("dummy.fodt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());

    // Turn on track changes and type "x".
    uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
    xPropertySet->setPropertyValue(u"RecordChanges"_ustr, uno::Any(true));
    m_nRedlineTableSizeChanged = 0;
    pWrtShell->Insert(u"x"_ustr);

    // 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(SwCursorSkipMode::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);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testRedlineUpdateCallback)
{
    // Load a document.
    createDoc("dummy.fodt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());

    // Turn on track changes, type "xx" and delete the second one.
    uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
    xPropertySet->setPropertyValue(u"RecordChanges"_ustr, uno::Any(true));
    pWrtShell->Insert(u"xx"_ustr);
    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(u"RecordChanges"_ustr, uno::Any(false));
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
    pWrtShell->Insert(u"This text is left of the redline"_ustr);

    // 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(SwCursorSkipMode::Chars, /*bSelect=*/false, 100/*Go enough right */, /*bBasicCall=*/false);
    pWrtShell->Insert(u"This text is right of the redline"_ustr);

    // No Modify callbacks
    CPPUNIT_ASSERT_EQUAL(3, m_nRedlineTableEntryModified);
}

static void addDarkLightThemes(const Color& rDarkColor, const Color& rLightColor)
{
    // Add a minimal dark scheme
    {
        svtools::EditableColorConfig aColorConfig;
        svtools::ColorConfigValue aValue;
        aValue.bIsVisible = true;
        aValue.nColor = rDarkColor;
        aColorConfig.SetColorValue(svtools::DOCCOLOR, aValue);
        aColorConfig.AddScheme(u"Dark"_ustr);
    }
    // Add a minimal light scheme
    {
        svtools::EditableColorConfig aColorConfig;
        svtools::ColorConfigValue aValue;
        aValue.bIsVisible = true;
        aValue.nColor = rLightColor;
        aColorConfig.SetColorValue(svtools::DOCCOLOR, aValue);
        aColorConfig.AddScheme(u"Light"_ustr);
    }
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testGetViewRenderState)
{
    addDarkLightThemes(COL_BLACK, COL_WHITE);
    SwXTextDocument* pXTextDocument = createDoc();
    int nFirstViewId = SfxLokHelper::getView();
    SwTestViewCallback aView1;
    {
        SwViewOption aViewOptions;
        aViewOptions.SetViewMetaChars(true);
        aViewOptions.SetOnlineSpell(true);
        getSwDocShell()->GetWrtShell()->ApplyViewOptions(aViewOptions);
    }
    CPPUNIT_ASSERT_EQUAL("PS;Default"_ostr, pXTextDocument->getViewRenderState());

    // Create a second view
    SfxLokHelper::createView();
    int nSecondViewId = SfxLokHelper::getView();
    SwTestViewCallback aView2;
    {
        // Give the second view different options
        SwViewOption aViewOptions;
        aViewOptions.SetViewMetaChars(false);
        aViewOptions.SetOnlineSpell(true);
        getSwDocShell()->GetWrtShell()->ApplyViewOptions(aViewOptions);
    }
    CPPUNIT_ASSERT_EQUAL("S;Default"_ostr, pXTextDocument->getViewRenderState());

    // Switch back to the first view, and check that the options string is the same
    SfxLokHelper::setView(nFirstViewId);
    CPPUNIT_ASSERT_EQUAL("PS;Default"_ostr, pXTextDocument->getViewRenderState());

    // Switch back to the second view, and change to dark mode
    SfxLokHelper::setView(nSecondViewId);
    {
        SwView* pView = getSwDocShell()->GetView();
        uno::Reference<frame::XFrame> xFrame = pView->GetViewFrame().GetFrame().GetFrameInterface();
        uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence({});
        comphelper::dispatchCommand(u".uno:ChangeTheme"_ustr, xFrame, aPropertyValues);
    }
    CPPUNIT_ASSERT_EQUAL("S;Dark"_ostr, pXTextDocument->getViewRenderState());
    // Switch back to the first view, and check that the options string is the same
    SfxLokHelper::setView(nFirstViewId);
    CPPUNIT_ASSERT_EQUAL("PS;Default"_ostr, pXTextDocument->getViewRenderState());
}

// Helper function to get a tile to a bitmap
static Bitmap getTile(SwXTextDocument* pXTextDocument)
{
    size_t nCanvasSize = 1024;
    size_t nTileSize = 256;
    std::vector<unsigned char> aPixmap(nCanvasSize * nCanvasSize * 4, 0);
    ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA);
    pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
    pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasSize, nCanvasSize),
            Fraction(1.0), Point(), aPixmap.data());
    pXTextDocument->paintTile(*pDevice, nCanvasSize, nCanvasSize, 0, 0, 15360, 7680);
    pDevice->EnableMapMode(false);
    return pDevice->GetBitmap(Point(0, 0), Size(nTileSize, nTileSize));
}

// Helper function to get a tile to a bitmap and check the pixel color
static Color getTilePixelColor(SwXTextDocument* pXTextDocument, int nPixelX, int nPixelY)
{
    Bitmap aBitmap = getTile(pXTextDocument);
    BitmapScopedReadAccess pAccess(aBitmap);
    Color aActualColor(pAccess->GetPixel(nPixelX, nPixelY));
    return aActualColor;
}

// Test that changing the theme in one view doesn't change it in the other view
CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testThemeViewSeparation)
{
    Color aDarkColor(0x1c, 0x1c, 0x1c);
    addDarkLightThemes(aDarkColor, COL_WHITE);
    SwXTextDocument* pXTextDocument = createDoc();
    int nFirstViewId = SfxLokHelper::getView();
    SwTestViewCallback aView1;
    // Set first view to light scheme
    {
        SwView* pView = getSwDocShell()->GetView();
        uno::Reference<frame::XFrame> xFrame = pView->GetViewFrame().GetFrame().GetFrameInterface();
        uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence(
            {
                { "NewTheme", uno::Any(u"Light"_ustr) },
            }
        );
        comphelper::dispatchCommand(u".uno:ChangeTheme"_ustr, xFrame, aPropertyValues);
    }
    // First view is in light scheme
    CPPUNIT_ASSERT_EQUAL(COL_WHITE, getTilePixelColor(pXTextDocument, 255, 255));
    // Create second view
    SfxLokHelper::createView();
    int nSecondViewId = SfxLokHelper::getView();
    SwTestViewCallback aView2;
    // Set second view to dark scheme
    {
        SwView* pView = getSwDocShell()->GetView();
        uno::Reference<frame::XFrame> xFrame = pView->GetViewFrame().GetFrame().GetFrameInterface();
        uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence(
            {
                { "NewTheme", uno::Any(u"Dark"_ustr) },
            }
        );
        comphelper::dispatchCommand(u".uno:ChangeTheme"_ustr, xFrame, aPropertyValues);
    }
    CPPUNIT_ASSERT_EQUAL(aDarkColor, getTilePixelColor(pXTextDocument, 255, 255));
    // First view still in light scheme
    SfxLokHelper::setView(nFirstViewId);
    CPPUNIT_ASSERT_EQUAL(COL_WHITE, getTilePixelColor(pXTextDocument, 255, 255));
    // Second view still in dark scheme
    SfxLokHelper::setView(nSecondViewId);
    CPPUNIT_ASSERT_EQUAL(aDarkColor, getTilePixelColor(pXTextDocument, 255, 255));
    // Switch second view back to light scheme
    {
        SwView* pView = getSwDocShell()->GetView();
        uno::Reference<frame::XFrame> xFrame = pView->GetViewFrame().GetFrame().GetFrameInterface();
        uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence(
            {
                { "NewTheme", uno::Any(u"Light"_ustr) },
            }
        );
        comphelper::dispatchCommand(u".uno:ChangeTheme"_ustr, xFrame, aPropertyValues);
    }
    // Now in light scheme
    CPPUNIT_ASSERT_EQUAL(COL_WHITE, getTilePixelColor(pXTextDocument, 255, 255));
}

// Test that changing the theme in one view doesn't change it in the other view
CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testInvertBackgroundViewSeparation)
{
    Color aDarkColor(0x1c, 0x1c, 0x1c);
    if (ThemeColors::UseOnlyWhiteDocBackground())
        aDarkColor = COL_WHITE;
    addDarkLightThemes(aDarkColor, COL_WHITE);
    SwXTextDocument* pXTextDocument = createDoc();
    int nFirstViewId = SfxLokHelper::getView();
    SwTestViewCallback aView1;
    // Set view to dark scheme
    {
        SwView* pView = getSwDocShell()->GetView();
        uno::Reference<frame::XFrame> xFrame = pView->GetViewFrame().GetFrame().GetFrameInterface();
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=94 H=97 G=95

¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.