/* -*- 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 "tiledrenderingmodeltestbase.cxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace css; namespace { void debugWriteImageToFile(unsigned step, const std::vector& pBuffer, unsigned nViewWidth, unsigned nViewHeight, const char* sJSON) { (void)step; (void)pBuffer; (void)nViewWidth; (void)nViewHeight; (void)sJSON; #if false printf ("%u %s\n\n", step, sJSON); BitmapEx aBitmapEx = vcl::bitmap::CreateFromData(pBuffer.data(), nViewWidth, nViewHeight, nViewWidth * 4, /*nBitsPerPixel*/32, true, true); OUString sFileName = "/tmp/slideshow_" + OUString::number(step) + ".png"; SvFileStream aStream(sFileName, StreamMode::WRITE | StreamMode::TRUNC); vcl::PngImageWriter aPNGWriter(aStream); aPNGWriter.write(aBitmapEx); #endif } } static std::ostream& operator<<(std::ostream& os, ViewShellId id) { os << static_cast(id); return os; } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testCreateDestroy) { createDoc("dummy.odp"); // Nothing to do, the tearDown call should cleanup. } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testCreateView) { createDoc("dummy.odp"); SfxLokHelper::createView(); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testRegisterCallback) { SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); setupLibreOfficeKitViewCallback(pViewShell->GetViewShellBase()); // Start text edit of the empty title shape. SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(0); SdrView* pView = pViewShell->GetView(); pView->SdrBeginTextEdit(pObject); CPPUNIT_ASSERT(pView->GetTextEditObject()); // 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(SdTiledRenderingTest, testPostKeyEvent) { SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(0); CPPUNIT_ASSERT_EQUAL(SdrObjKind::TitleText, pObject->GetObjIdentifier()); SdrTextObj* pTextObj = static_cast(pObject); SdrView* pView = pViewShell->GetView(); pView->MarkObj(pTextObj, pView->GetSdrPageView()); SfxStringItem aInputString(SID_ATTR_CHAR, u"x"_ustr); pViewShell->GetViewFrame()->GetDispatcher()->ExecuteList(SID_ATTR_CHAR, SfxCallMode::SYNCHRON, { &aInputString }); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(pView->GetTextEditObject()); EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); // Did we manage to enter a second character? CPPUNIT_ASSERT_EQUAL(static_cast(2), rEditView.GetSelection().start.nIndex); ESelection aWordSelection(0, 0, 0, 2); // start para, start char, end para, end char. rEditView.SetSelection(aWordSelection); // Did we enter the expected character? CPPUNIT_ASSERT_EQUAL(u"xx"_ustr, rEditView.GetSelected()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testPostMouseEvent) { SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(0); CPPUNIT_ASSERT_EQUAL(SdrObjKind::TitleText, pObject->GetObjIdentifier()); SdrTextObj* pTextObj = static_cast(pObject); SdrView* pView = pViewShell->GetView(); pView->MarkObj(pTextObj, pView->GetSdrPageView()); SfxStringItem aInputString(SID_ATTR_CHAR, u"x"_ustr); pViewShell->GetViewFrame()->GetDispatcher()->ExecuteList(SID_ATTR_CHAR, SfxCallMode::SYNCHRON, { &aInputString }); CPPUNIT_ASSERT(pView->GetTextEditObject()); EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); // Did we manage to go after the first character? CPPUNIT_ASSERT_EQUAL(static_cast(1), rEditView.GetSelection().start.nIndex); vcl::Cursor* pCursor = rEditView.GetCursor(); Point aPosition(pCursor->GetPos().getX(), pCursor->GetPos().getY() + pCursor->GetSize().Height() / 2); aPosition.setX(aPosition.getX() - 1000); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, o3tl::toTwips(aPosition.getX(), o3tl::Length::mm100), o3tl::toTwips(aPosition.getY(), o3tl::Length::mm100), 1, MOUSE_LEFT, 0); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, o3tl::toTwips(aPosition.getX(), o3tl::Length::mm100), o3tl::toTwips(aPosition.getY(), o3tl::Length::mm100), 1, MOUSE_LEFT, 0); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(pView->GetTextEditObject()); // The new cursor position must be before the first word. CPPUNIT_ASSERT_EQUAL(static_cast(0), rEditView.GetSelection().start.nIndex); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSetTextSelection) { SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); uno::Reference xDrawPage(pXImpressDocument->getDrawPages()->getByIndex(0), uno::UNO_QUERY); uno::Reference xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY); xShape->setString(u"Aaa bbb."_ustr); // Create a selection on the second word. sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(0); SdrView* pView = pViewShell->GetView(); pView->SdrBeginTextEdit(pObject); CPPUNIT_ASSERT(pView->GetTextEditObject()); EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); ESelection aWordSelection(0, 4, 0, 7); rEditView.SetSelection(aWordSelection); // Did we indeed manage to select the second word? CPPUNIT_ASSERT_EQUAL(u"bbb"_ustr, rEditView.GetSelected()); // Now use setTextSelection() to move the end of the selection 1000 twips right. vcl::Cursor* pCursor = rEditView.GetCursor(); Point aEnd = pCursor->GetPos(); aEnd.setX(aEnd.getX() + 1000); pXImpressDocument->setTextSelection(LOK_SETTEXTSELECTION_END, aEnd.getX(), aEnd.getY()); // The new selection must include the ending dot, too -- but not the first word. CPPUNIT_ASSERT_EQUAL(u"bbb."_ustr, rEditView.GetSelected()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testGetTextSelection) { SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); uno::Reference xDrawPage(pXImpressDocument->getDrawPages()->getByIndex(0), uno::UNO_QUERY); uno::Reference xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY); xShape->setString(u"Shape"_ustr); // Create a selection on the shape text. sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(0); SdrView* pView = pViewShell->GetView(); pView->SdrBeginTextEdit(pObject); CPPUNIT_ASSERT(pView->GetTextEditObject()); EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); ESelection aWordSelection(0, 0, 0, 5); rEditView.SetSelection(aWordSelection); // Did we indeed manage to copy the selected text? CPPUNIT_ASSERT_EQUAL("Shape"_ostr, apitest::helper::transferable::getTextSelection(pXImpressDocument->getSelection(), "text/plain;charset=utf-8"_ostr)); // Make sure returned RTF is not empty. CPPUNIT_ASSERT(!apitest::helper::transferable::getTextSelection(pXImpressDocument->getSelection(), "text/rtf"_ostr).isEmpty()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSetGraphicSelection) { SdXImpressDocument* pXImpressDocument = createDoc("shape.odp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pPage = pViewShell->GetActualPage(); SdrObject* pObject = pPage->GetObj(0); 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(8), handleList.GetHdlCount()); // Take the bottom center one. SdrHdl* pHdl = handleList.GetHdl(6); CPPUNIT_ASSERT_EQUAL(int(SdrHdlKind::Lower), static_cast(pHdl->GetKind())); ::tools::Rectangle aShapeBefore = pObject->GetSnapRect(); // Resize. pXImpressDocument->setGraphicSelection(LOK_SETGRAPHICSELECTION_START, o3tl::toTwips(pHdl->GetPos().getX(), o3tl::Length::mm100), o3tl::toTwips(pHdl->GetPos().getY(), o3tl::Length::mm100)); pXImpressDocument->setGraphicSelection(LOK_SETGRAPHICSELECTION_END, o3tl::toTwips(pHdl->GetPos().getX(), o3tl::Length::mm100), o3tl::toTwips(pHdl->GetPos().getY() + 1000, o3tl::Length::mm100)); // Assert that view shell ID tracking works. sal_Int32 nView1 = SfxLokHelper::getView(); SdDrawDocument* pDocument = pXImpressDocument->GetDoc(); sd::UndoManager* pUndoManager = pDocument->GetUndoManager(); CPPUNIT_ASSERT_EQUAL(static_cast(1), pUndoManager->GetUndoActionCount()); auto pListAction = dynamic_cast(pUndoManager->GetUndoAction()); CPPUNIT_ASSERT(pListAction); for (size_t i = 0; i < pListAction->maUndoActions.size(); ++i) // The second item was -1 here, view shell ID wasn't known. CPPUNIT_ASSERT_EQUAL(ViewShellId(nView1), pListAction->GetUndoAction(i)->GetViewShellId()); ::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(aShapeBefore.getOpenHeight() < aShapeAfter.getOpenHeight()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testUndoShells) { // Load a document and set the page size. SdXImpressDocument* pXImpressDocument = createDoc("shape.odp"); uno::Sequence aPropertyValues(comphelper::InitPropertySequence( { {"AttributePageSize.Width", uno::Any(static_cast(10000))}, {"AttributePageSize.Height", uno::Any(static_cast(10000))}, })); dispatchCommand(mxComponent, u".uno:AttributePageSize"_ustr, aPropertyValues); // Assert that view shell ID tracking works for SdUndoAction subclasses. SdDrawDocument* pDocument = pXImpressDocument->GetDoc(); sd::UndoManager* pUndoManager = pDocument->GetUndoManager(); CPPUNIT_ASSERT_EQUAL(static_cast(1), pUndoManager->GetUndoActionCount()); sal_Int32 nView1 = SfxLokHelper::getView(); // This was -1, SdUndoGroup did not track what view shell created it. CPPUNIT_ASSERT_EQUAL(ViewShellId(nView1), pUndoManager->GetUndoAction()->GetViewShellId()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testResetSelection) { SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); uno::Reference xDrawPage(pXImpressDocument->getDrawPages()->getByIndex(0), uno::UNO_QUERY); uno::Reference xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY); xShape->setString(u"Aaa bbb."_ustr); // Create a selection on the second word. sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(0); SdrView* pView = pViewShell->GetView(); pView->SdrBeginTextEdit(pObject); CPPUNIT_ASSERT(pView->GetTextEditObject()); EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); ESelection aWordSelection(0, 4, 0, 7); rEditView.SetSelection(aWordSelection); // Did we indeed manage to select the second word? CPPUNIT_ASSERT_EQUAL(u"bbb"_ustr, rEditView.GetSelected()); // Now use resetSelection() to reset the selection. pXImpressDocument->resetSelection(); CPPUNIT_ASSERT(!pView->GetTextEditObject()); } namespace { std::vector getCurrentParts(SdXImpressDocument* pDocument) { int parts = pDocument->getParts(); std::vector result; result.reserve(parts); for (int i = 0; i < parts; i++) { result.push_back(pDocument->getPartName(i)); } return result; } } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testInsertDeletePage) { SdXImpressDocument* pXImpressDocument = createDoc("insert-delete.odp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); setupLibreOfficeKitViewCallback(pViewShell->GetViewShellBase()); SdDrawDocument* pDoc = pXImpressDocument->GetDocShell()->GetDoc(); CPPUNIT_ASSERT(pDoc); std::vector aInserted = { u"Slide 1"_ustr, u"Slide 2"_ustr, u"Slide 3"_ustr, u"Slide 4"_ustr, u"Slide 5"_ustr, u"Slide 6"_ustr, u"Slide 7"_ustr, u"Slide 8"_ustr, u"Slide 9"_ustr, u"Slide 10"_ustr, u"Slide 11"_ustr }; std::vector aDeleted = { u"Slide 1"_ustr }; // the document has 1 slide CPPUNIT_ASSERT_EQUAL(static_cast(1), pDoc->GetSdPageCount(PageKind::Standard)); uno::Sequence aArgs; // Insert slides m_aDocumentSizeCondition.reset(); for (unsigned it = 1; it <= 10; it++) dispatchCommand(mxComponent, u".uno:InsertPage"_ustr, aArgs); osl::Condition::Result aResult = m_aDocumentSizeCondition.wait(std::chrono::seconds(2)); CPPUNIT_ASSERT_EQUAL(osl::Condition::result_ok, aResult); // Verify inserted slides std::vector aPageList(getCurrentParts(pXImpressDocument)); CPPUNIT_ASSERT_EQUAL(aPageList.size(), aInserted.size()); for (auto it1 = aPageList.begin(), it2 = aInserted.begin(); it1 != aPageList.end(); ++it1, ++it2) { CPPUNIT_ASSERT_EQUAL(*it1, *it2); } // Delete slides m_aDocumentSizeCondition.reset(); for (unsigned it = 1; it <= 10; it++) dispatchCommand(mxComponent, u".uno:DeletePage"_ustr, aArgs); aResult = m_aDocumentSizeCondition.wait(std::chrono::seconds(2)); CPPUNIT_ASSERT_EQUAL(osl::Condition::result_ok, aResult); // Verify deleted slides aPageList = getCurrentParts(pXImpressDocument); CPPUNIT_ASSERT_EQUAL(aPageList.size(), aDeleted.size()); for (auto it1 = aPageList.begin(), it2 = aDeleted.begin(); it1 != aPageList.end(); ++it1, ++it2) { CPPUNIT_ASSERT_EQUAL(*it1, *it2); } // Undo deleted slides m_aDocumentSizeCondition.reset(); for (unsigned it = 1; it <= 10; it++) dispatchCommand(mxComponent, u".uno:Undo"_ustr, aArgs); aResult = m_aDocumentSizeCondition.wait(std::chrono::seconds(2)); CPPUNIT_ASSERT_EQUAL(osl::Condition::result_ok, aResult); // Verify inserted slides aPageList = getCurrentParts(pXImpressDocument); CPPUNIT_ASSERT_EQUAL(aPageList.size(), aInserted.size()); for (auto it1 = aPageList.begin(), it2 = aInserted.begin(); it1 != aPageList.end(); ++it1, ++it2) { CPPUNIT_ASSERT_EQUAL(*it1, *it2); } // Redo deleted slides m_aDocumentSizeCondition.reset(); for (unsigned it = 1; it <= 10; it++) dispatchCommand(mxComponent, u".uno:Redo"_ustr, aArgs); aResult = m_aDocumentSizeCondition.wait(std::chrono::seconds(2)); CPPUNIT_ASSERT_EQUAL(osl::Condition::result_ok, aResult); // Verify deleted slides aPageList = getCurrentParts(pXImpressDocument); CPPUNIT_ASSERT_EQUAL(aPageList.size(), aDeleted.size()); for (auto it1 = aPageList.begin(), it2 = aDeleted.begin(); it1 != aPageList.end(); ++it1, ++it2) { CPPUNIT_ASSERT_EQUAL(*it1, *it2); } // the document has 1 slide CPPUNIT_ASSERT_EQUAL(static_cast(1), pDoc->GetSdPageCount(PageKind::Standard)); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testInsertTable) { SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); uno::Sequence aArgs(comphelper::InitPropertySequence( { { "Rows", uno::Any(sal_Int32(3)) }, { "Columns", uno::Any(sal_Int32(5)) } })); dispatchCommand(mxComponent, u".uno:InsertTable"_ustr, aArgs); // get the table sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(1); CPPUNIT_ASSERT(pObject); // check that the table is not in the top left corner Point aPos(pObject->GetRelativePos()); CPPUNIT_ASSERT(aPos.X() != 0); CPPUNIT_ASSERT(aPos.Y() != 0); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testDeleteTable) { SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); uno::Sequence aArgs(comphelper::InitPropertySequence( { { "Rows", uno::Any(sal_Int32(3)) }, { "Columns", uno::Any(sal_Int32(5)) } })); dispatchCommand(mxComponent, u".uno:InsertTable"_ustr, aArgs); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdrView* pSdrView = pViewShell->GetView(); const SdrMarkList& rMarkList = pSdrView->GetMarkedObjectList(); CPPUNIT_ASSERT(rMarkList.GetMarkCount()); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_MOD1 | awt::Key::A); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_MOD1 | awt::Key::A); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DELETE); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DELETE); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(!rMarkList.GetMarkCount()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testPartHash) { SdXImpressDocument* pDoc = createDoc("dummy.odp"); int nParts = pDoc->getParts(); for (int it = 0; it < nParts; it++) { CPPUNIT_ASSERT(!pDoc->getPartHash(it).isEmpty()); } // check part that it does not exists CPPUNIT_ASSERT(pDoc->getPartHash(100).isEmpty()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testResizeTable) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("table.odp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(0); auto pTableObject = dynamic_cast(pObject); CPPUNIT_ASSERT(pTableObject); // Select the table by marking it + starting and ending text edit. SdrView* pView = pViewShell->GetView(); pView->MarkObj(pObject, pView->GetSdrPageView()); pView->SdrBeginTextEdit(pObject); pView->SdrEndTextEdit(); // Remember the original row heights. uno::Reference xTable = pTableObject->getTable(); uno::Reference xRows = xTable->getRows(); uno::Reference xRow1(xRows->getByIndex(0), uno::UNO_QUERY); sal_Int32 nExpectedRow1 = xRow1->getPropertyValue(u"Size"_ustr).get(); uno::Reference xRow2(xRows->getByIndex(1), uno::UNO_QUERY); sal_Int32 nExpectedRow2 = xRow2->getPropertyValue(u"Size"_ustr).get(); // Resize the upper row, decrease its height by 1 cm. Point aInnerRowEdge = pObject->GetSnapRect().Center(); pXImpressDocument->setGraphicSelection(LOK_SETGRAPHICSELECTION_START, o3tl::toTwips(aInnerRowEdge.getX(), o3tl::Length::mm100), o3tl::toTwips(aInnerRowEdge.getY(), o3tl::Length::mm100)); pXImpressDocument->setGraphicSelection(LOK_SETGRAPHICSELECTION_END, o3tl::toTwips(aInnerRowEdge.getX(), o3tl::Length::mm100), o3tl::toTwips(aInnerRowEdge.getY() - 1000, o3tl::Length::mm100)); // Remember the resized row heights. sal_Int32 nResizedRow1 = xRow1->getPropertyValue(u"Size"_ustr).get(); CPPUNIT_ASSERT(nResizedRow1 < nExpectedRow1); sal_Int32 nResizedRow2 = xRow2->getPropertyValue(u"Size"_ustr).get(); CPPUNIT_ASSERT_EQUAL(nExpectedRow2, nResizedRow2); // Now undo the resize. pXImpressDocument->GetDocShell()->GetUndoManager()->Undo(); // Check the undo result. sal_Int32 nActualRow1 = xRow1->getPropertyValue(u"Size"_ustr).get(); CPPUNIT_ASSERT_EQUAL(nExpectedRow1, nActualRow1); sal_Int32 nActualRow2 = xRow2->getPropertyValue(u"Size"_ustr).get(); // Expected was 4000, actual was 4572, i.e. the second row after undo was larger than expected. CPPUNIT_ASSERT_EQUAL(nExpectedRow2, nActualRow2); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testResizeTableColumn) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("table-column.odp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(0); auto pTableObject = dynamic_cast(pObject); CPPUNIT_ASSERT(pTableObject); // Select the table by marking it + starting and ending text edit. SdrView* pView = pViewShell->GetView(); pView->MarkObj(pObject, pView->GetSdrPageView()); pView->SdrBeginTextEdit(pObject); pView->SdrEndTextEdit(); // Remember the original cell widths. xmlDocUniquePtr pXmlDoc = parseXmlDump(); OString aPrefix = "/SdDrawDocument/SdrModel/maPages/SdPage/SdrPage/SdrObjList/SdrTableObj/SdrTableObjImpl/TableLayouter/columns/"_ostr; sal_Int32 nExpectedColumn1 = getXPath(pXmlDoc, aPrefix + "TableLayouter_Layout[1]", "size").toInt32(); sal_Int32 nExpectedColumn2 = getXPath(pXmlDoc, aPrefix + "TableLayouter_Layout[2]", "size").toInt32(); pXmlDoc = nullptr; // Resize the left column, decrease its width by 1 cm. Point aInnerRowEdge = pObject->GetSnapRect().Center(); pXImpressDocument->setGraphicSelection(LOK_SETGRAPHICSELECTION_START, o3tl::toTwips(aInnerRowEdge.getX(), o3tl::Length::mm100), o3tl::toTwips(aInnerRowEdge.getY(), o3tl::Length::mm100)); pXImpressDocument->setGraphicSelection(LOK_SETGRAPHICSELECTION_END, o3tl::toTwips(aInnerRowEdge.getX() - 1000, o3tl::Length::mm100), o3tl::toTwips(aInnerRowEdge.getY(), o3tl::Length::mm100)); // Remember the resized column widths. pXmlDoc = parseXmlDump(); sal_Int32 nResizedColumn1 = getXPath(pXmlDoc, aPrefix + "TableLayouter_Layout[1]", "size").toInt32(); CPPUNIT_ASSERT(nResizedColumn1 < nExpectedColumn1); sal_Int32 nResizedColumn2 = getXPath(pXmlDoc, aPrefix + "TableLayouter_Layout[2]", "size").toInt32(); CPPUNIT_ASSERT(nResizedColumn2 > nExpectedColumn2); pXmlDoc = nullptr; // Now undo the resize. pXImpressDocument->GetDocShell()->GetUndoManager()->Undo(); // Check the undo result. pXmlDoc = parseXmlDump(); sal_Int32 nActualColumn1 = getXPath(pXmlDoc, aPrefix + "TableLayouter_Layout[1]", "size").toInt32(); // Expected was 7049, actual was 6048, i.e. the first column width after undo was 1cm smaller than expected. CPPUNIT_ASSERT_EQUAL(nExpectedColumn1, nActualColumn1); sal_Int32 nActualColumn2 = getXPath(pXmlDoc, aPrefix + "TableLayouter_Layout[2]", "size").toInt32(); CPPUNIT_ASSERT_EQUAL(nExpectedColumn2, nActualColumn2); pXmlDoc = nullptr; } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testViewCursors) { // Create two views. SdXImpressDocument* pXImpressDocument = createDoc("shape.odp"); ViewCallback aView1; SfxLokHelper::createView(); ViewCallback aView2; // Select the shape in the second view. sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(0); SdrView* pView = pViewShell->GetView(); pView->MarkObj(pObject, pView->GetSdrPageView()); Scheduler::ProcessEventsToIdle(); // First view notices that there was a selection change in the other view. CPPUNIT_ASSERT(aView1.m_bGraphicViewSelectionInvalidated); // Second view notices that there was a selection change in its own view. CPPUNIT_ASSERT(aView2.m_bGraphicSelectionInvalidated); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testViewCursorParts) { // Create two views. SdXImpressDocument* pXImpressDocument = createDoc("shape.odp"); ViewCallback aView1; SfxLokHelper::createView(); pXImpressDocument->initializeForTiledRendering(uno::Sequence()); ViewCallback aView2; // Select the shape in the second view. sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(0); SdrView* pView = pViewShell->GetView(); pView->MarkObj(pObject, pView->GetSdrPageView()); Scheduler::ProcessEventsToIdle(); // First view notices that there was a selection change in the other view. CPPUNIT_ASSERT(aView1.m_bGraphicViewSelectionInvalidated); pView->UnmarkAllObj(pView->GetSdrPageView()); // Now switch to the second part in the second view. pXImpressDocument->setPart(1); aView2.m_nPart = 1; aView1.m_bGraphicViewSelectionInvalidated = false; pActualPage = pViewShell->GetActualPage(); pObject = pActualPage->GetObj(0); pView->MarkObj(pObject, pView->GetSdrPageView()); Scheduler::ProcessEventsToIdle(); // First view ignores view selection, as it would be for part 1, and it's in part 0. // This failed when the "part" was always 0 in the callback. CPPUNIT_ASSERT(!aView1.m_bGraphicViewSelectionInvalidated); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testCursorViews) { // Create the first view. SdXImpressDocument* pXImpressDocument = createDoc("title-shape.odp"); int nView1 = SfxLokHelper::getView(); ViewCallback aView1; // Begin text edit on the only object on the slide. sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdrView* pView = pViewShell->GetView(); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(pView->IsTextEdit()); // Make sure that cursor state is not changed just because we create a second view. aView1.m_bCursorVisibleChanged = false; SfxLokHelper::createView(); pXImpressDocument->initializeForTiledRendering(uno::Sequence()); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(!aView1.m_bCursorVisibleChanged); // Make sure that typing in the first view causes an invalidation in the // second view as well, even if the second view was created after begin // text edit in the first view. ViewCallback aView2; // This failed: the second view didn't get a lock notification, even if the // first view already started text edit. CPPUNIT_ASSERT(aView2.m_bViewLock); SfxLokHelper::setView(nView1); aView2.m_bTilesInvalidated = false; pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0); Scheduler::ProcessEventsToIdle(); // This failed: the second view was not invalidated when pressing a key in // the first view. CPPUNIT_ASSERT(aView2.m_bTilesInvalidated); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testCursorVisibility_SingleClick) { // Single-clicking in a text box enters editing only // when it's on the text, even if it's the default text. // Load doc. SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); ViewCallback aView1; // Begin text edit on the only object on the slide. sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject1 = pActualPage->GetObj(0); CPPUNIT_ASSERT(pObject1 != nullptr); CPPUNIT_ASSERT_EQUAL(SdrObjKind::TitleText, pObject1->GetObjIdentifier()); SdrTextObj* pTextObject = static_cast(pObject1); // Click once outside of the text (in the first quartile) => no editing. const ::tools::Rectangle aRect = pTextObject->GetCurrentBoundRect(); const auto cornerX = o3tl::toTwips(aRect.Left() + (aRect.getOpenWidth() / 4), o3tl::Length::mm100); const auto cornerY = o3tl::toTwips(aRect.Top() + (aRect.getOpenHeight() / 4), o3tl::Length::mm100); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, cornerX, cornerY, 1, MOUSE_LEFT, 0); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, cornerX, cornerY, 1, MOUSE_LEFT, 0); Scheduler::ProcessEventsToIdle(); // No editing. CPPUNIT_ASSERT(!pViewShell->GetView()->IsTextEdit()); CPPUNIT_ASSERT(!aView1.m_bCursorVisible); // Click again, now on the text, in the center, to start editing. const auto centerX = o3tl::toTwips(aRect.Left() + (aRect.getOpenWidth() / 2), o3tl::Length::mm100); const auto centerY = o3tl::toTwips(aRect.Top() + (aRect.getOpenHeight() / 2), o3tl::Length::mm100); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, centerX, centerY, 1, MOUSE_LEFT, 0); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, centerX, centerY, 1, MOUSE_LEFT, 0); Scheduler::ProcessEventsToIdle(); // We must be in text editing mode and have cursor visible. CPPUNIT_ASSERT(pViewShell->GetView()->IsTextEdit()); CPPUNIT_ASSERT(aView1.m_bCursorVisible); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testCursorVisibility_DoubleClick) { // Double-clicking anywhere in the TextBox should start editing. // Create the first view. SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); ViewCallback aView1; // Begin text edit on the only object on the slide. sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject1 = pActualPage->GetObj(0); CPPUNIT_ASSERT(pObject1 != nullptr); CPPUNIT_ASSERT_EQUAL(SdrObjKind::TitleText, pObject1->GetObjIdentifier()); SdrTextObj* pTextObject = static_cast(pObject1); // Double-click outside the text to enter edit mode. const ::tools::Rectangle aRect = pTextObject->GetCurrentBoundRect(); const auto cornerX = o3tl::toTwips(aRect.Left() + (aRect.getOpenWidth() / 4), o3tl::Length::mm100); const auto cornerY = o3tl::toTwips(aRect.Top() + (aRect.getOpenHeight() / 4), o3tl::Length::mm100); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, cornerX, cornerY, 2, MOUSE_LEFT, 0); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, cornerX, cornerY, 2, MOUSE_LEFT, 0); Scheduler::ProcessEventsToIdle(); // We must be in text editing mode and have cursor visible. CPPUNIT_ASSERT(pViewShell->GetView()->IsTextEdit()); CPPUNIT_ASSERT(aView1.m_bCursorVisible); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testCursorVisibility_MultiView) { // Create the first view. SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); const int nView1 = SfxLokHelper::getView(); ViewCallback aView1; // Begin text edit on the only object on the slide. sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject1 = pActualPage->GetObj(0); CPPUNIT_ASSERT(pObject1); CPPUNIT_ASSERT_EQUAL(SdrObjKind::TitleText, pObject1->GetObjIdentifier()); SdrTextObj* pTextObject = static_cast(pObject1); // Make sure that cursor state is not changed just because we create a second view. SfxLokHelper::createView(); pXImpressDocument->initializeForTiledRendering(uno::Sequence()); const int nView2 = SfxLokHelper::getView(); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(false, aView1.m_bCursorVisibleChanged); CPPUNIT_ASSERT_EQUAL(false, aView1.m_aViewCursorVisibilities[nView2]); // Also check that the second view gets the notifications. ViewCallback aView2; SfxLokHelper::setView(nView1); ::tools::Rectangle aRect = pTextObject->GetCurrentBoundRect(); const auto centerX = o3tl::toTwips(aRect.Left() + (aRect.getOpenWidth() / 2), o3tl::Length::mm100); const auto centerY = o3tl::toTwips(aRect.Top() + (aRect.getOpenHeight() / 2), o3tl::Length::mm100); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, centerX, centerY, 2, MOUSE_LEFT, 0); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, centerX, centerY, 2, MOUSE_LEFT, 0); Scheduler::ProcessEventsToIdle(); // We must be in text editing mode and have cursor visible. CPPUNIT_ASSERT(pViewShell->GetView()->IsTextEdit()); CPPUNIT_ASSERT(aView1.m_bCursorVisible); CPPUNIT_ASSERT_EQUAL(false, aView1.m_aViewCursorVisibilities[nView2]); CPPUNIT_ASSERT_EQUAL(false, aView2.m_bCursorVisible); CPPUNIT_ASSERT_EQUAL(false, aView2.m_aViewCursorVisibilities[nView1]); CPPUNIT_ASSERT_EQUAL(false, aView2.m_aViewCursorVisibilities[nView2]); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testCursorVisibility_Escape) { // Load doc. SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); ViewCallback aView1; // Begin text edit on the only object on the slide. sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject1 = pActualPage->GetObj(0); CPPUNIT_ASSERT(pObject1 != nullptr); CPPUNIT_ASSERT_EQUAL(SdrObjKind::TitleText, pObject1->GetObjIdentifier()); SdrTextObj* pTextObject = static_cast(pObject1); // Click once on the text to start editing. const ::tools::Rectangle aRect = pTextObject->GetCurrentBoundRect(); const auto centerX = o3tl::toTwips(aRect.Left() + (aRect.getOpenWidth() / 2), o3tl::Length::mm100); const auto centerY = o3tl::toTwips(aRect.Top() + (aRect.getOpenHeight() / 2), o3tl::Length::mm100); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, centerX, centerY, 1, MOUSE_LEFT, 0); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, centerX, centerY, 1, MOUSE_LEFT, 0); Scheduler::ProcessEventsToIdle(); // We must be in text editing mode and have cursor visible. CPPUNIT_ASSERT(pViewShell->GetView()->IsTextEdit()); CPPUNIT_ASSERT(aView1.m_bCursorVisible); // End editing by pressing the escape key. pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::ESCAPE); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::ESCAPE); Scheduler::ProcessEventsToIdle(); // We must be in text editing mode and have cursor visible. CPPUNIT_ASSERT(!pViewShell->GetView()->IsTextEdit()); CPPUNIT_ASSERT_EQUAL(false, aView1.m_bCursorVisible); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testViewLock) { // Load a document that has a shape and create two views. SdXImpressDocument* pXImpressDocument = createDoc("shape.odp"); ViewCallback aView1; SfxLokHelper::createView(); pXImpressDocument->initializeForTiledRendering(uno::Sequence()); // Begin text edit in the second view and assert that the first gets a lock // notification. sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(0); SdrView* pView = pViewShell->GetView(); aView1.m_bViewLock = false; pView->SdrBeginTextEdit(pObject); CPPUNIT_ASSERT(aView1.m_bViewLock); // End text edit in the second view, and assert that the lock is removed in // the first view. pView->SdrEndTextEdit(); CPPUNIT_ASSERT(!aView1.m_bViewLock); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testUndoLimiting) { // Create the first view. SdXImpressDocument* pXImpressDocument = createDoc("title-shape.odp"); sd::ViewShell* pViewShell1 = pXImpressDocument->GetDocShell()->GetViewShell(); int nView1 = SfxLokHelper::getView(); SfxLokHelper::createView(); sd::ViewShell* pViewShell2 = pXImpressDocument->GetDocShell()->GetViewShell(); CPPUNIT_ASSERT(pViewShell1 != pViewShell2); // Begin text edit on the only object on the slide. SfxLokHelper::setView(nView1); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(pViewShell1->GetView()->IsTextEdit()); // View2 UNDO stack should be empty { SfxRequest aReq2(SID_UNDO, SfxCallMode::SLOT, pXImpressDocument->GetDocShell()->GetDoc()->GetPool()); aReq2.AppendItem(SfxUInt16Item(SID_UNDO, 1)); pViewShell2->ExecuteSlot(aReq2); const auto* pReturnValue = aReq2.GetReturnValue().getItem(); CPPUNIT_ASSERT(!pReturnValue); } // View1 can UNDO { SfxRequest aReq1(SID_UNDO, SfxCallMode::SLOT, pXImpressDocument->GetDocShell()->GetDoc()->GetPool()); aReq1.AppendItem(SfxUInt16Item(SID_UNDO, 1)); pViewShell1->ExecuteSlot(aReq1); CPPUNIT_ASSERT(aReq1.IsDone()); } // View1 can REDO { SfxRequest aReq1(SID_REDO, SfxCallMode::SLOT, pXImpressDocument->GetDocShell()->GetDoc()->GetPool()); aReq1.AppendItem(SfxUInt16Item(SID_REDO, 1)); pViewShell1->ExecuteSlot(aReq1); CPPUNIT_ASSERT(aReq1.IsDone()); } // Exit text edit mode pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::ESCAPE); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::ESCAPE); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(!pViewShell1->GetView()->IsTextEdit()); // Now check view2 cannot undo actions. { SfxRequest aReq2(SID_UNDO, SfxCallMode::SLOT, pXImpressDocument->GetDocShell()->GetDoc()->GetPool()); aReq2.AppendItem(SfxUInt16Item(SID_UNDO, 1)); pViewShell2->ExecuteSlot(aReq2); const SfxUInt32Item* pUInt32Item = dynamic_cast(aReq2.GetReturnValue().getItem()); CPPUNIT_ASSERT(pUInt32Item); CPPUNIT_ASSERT_EQUAL(static_cast< sal_uInt32 >(SID_REPAIRPACKAGE), pUInt32Item->GetValue()); } // Now check view1 can undo action { SfxRequest aReq1(SID_UNDO, SfxCallMode::SLOT, pXImpressDocument->GetDocShell()->GetDoc()->GetPool()); aReq1.AppendItem(SfxUInt16Item(SID_UNDO, 1)); pViewShell1->ExecuteSlot(aReq1); CPPUNIT_ASSERT(aReq1.IsDone()); } } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testCreateViewGraphicSelection) { // Load a document and register a callback. SdXImpressDocument* pXImpressDocument = createDoc("shape.odp"); ViewCallback aView1; // Select the only shape in the document and assert that the graphic selection is changed. sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(0); SdrView* pView = pViewShell->GetView(); aView1.m_bGraphicSelectionInvalidated = false; pView->MarkObj(pObject, pView->GetSdrPageView()); CPPUNIT_ASSERT(aView1.m_bGraphicSelectionInvalidated); // Now create a new view. aView1.m_bGraphicSelectionInvalidated = false; SfxLokHelper::createView(); pXImpressDocument->initializeForTiledRendering({}); // This failed, creating a new view affected the graphic selection of an // existing view. CPPUNIT_ASSERT(!aView1.m_bGraphicSelectionInvalidated); // Check that when the first view has a shape selected and we register a // callback on the second view, then it gets a "graphic view selection". ViewCallback aView2; // This failed, the created new view had no "view selection" of the first // view's selected shape. CPPUNIT_ASSERT(aView2.m_bGraphicViewSelectionInvalidated); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testCreateViewTextCursor) { // Load a document and register a callback. SdXImpressDocument* pXImpressDocument = createDoc("title-shape.odp"); ViewCallback aView1; // Begin text edit. pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0); Scheduler::ProcessEventsToIdle(); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdrView* pSdrView = pViewShell->GetView(); CPPUNIT_ASSERT(pSdrView->IsTextEdit()); // Create an editeng text selection. EditView& rEditView = pSdrView->GetTextEditOutlinerView()->GetEditView(); // 0th para, 0th char -> 0th para, 1st char. ESelection aWordSelection(0, 0, 0, 1); rEditView.SetSelection(aWordSelection); // Make sure that creating a new view either doesn't affect the previous // one, or at least the effect is not visible at the end. aView1.m_aViewCursorInvalidations.clear(); aView1.m_aViewCursorVisibilities.clear(); SfxLokHelper::createView(); pXImpressDocument->initializeForTiledRendering({}); ViewCallback aView2; bool bFoundCursor = false; for (const auto& rInvalidation : aView1.m_aViewCursorInvalidations) { auto itVisibility = aView1.m_aViewCursorVisibilities.find(rInvalidation.first); // For each cursor invalidation: if there is no visibility or the visibility is true, that's a problem. if (itVisibility == aView1.m_aViewCursorVisibilities.end() || itVisibility->second) { bFoundCursor = true; break; } } // This failed: the second view created an unexpected view cursor in the // first view. CPPUNIT_ASSERT(!bFoundCursor); // This failed: the text view selection of the first view wasn't seen by // the second view. CPPUNIT_ASSERT(aView2.m_bViewSelectionSet); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testTdf102223) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("tdf102223.odp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); auto pTableObject = dynamic_cast(pActualPage->GetObj(2)); CPPUNIT_ASSERT(pTableObject); SdrView* pView = pViewShell->GetView(); // select contents of cell ::tools::Rectangle aRect = pTableObject->GetCurrentBoundRect(); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, o3tl::toTwips(aRect.Left() + 2, o3tl::Length::mm100), o3tl::toTwips(aRect.Top() + 2, o3tl::Length::mm100), 1, MOUSE_LEFT, 0); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, o3tl::toTwips(aRect.Left() + 2, o3tl::Length::mm100), o3tl::toTwips(aRect.Top() + 2, o3tl::Length::mm100), 1, MOUSE_LEFT, 0); Scheduler::ProcessEventsToIdle(); pView->SdrBeginTextEdit(pTableObject); CPPUNIT_ASSERT(pView->GetTextEditObject()); EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); rEditView.SetSelection(ESelection(0, 0, 0, 3)); // start para, start char, end para, end char. CPPUNIT_ASSERT_EQUAL(u"Red"_ustr, rEditView.GetSelected()); CPPUNIT_ASSERT_EQUAL( int(1411), static_cast(rEditView.GetAttribs().Get(EE_CHAR_FONTHEIGHT).GetHeight())); // cut contents of cell uno::Sequence aArgs; dispatchCommand(mxComponent, u".uno:Cut"_ustr, aArgs); pView->SdrEndTextEdit(false); pView->SdrBeginTextEdit(pTableObject); CPPUNIT_ASSERT(pView->GetTextEditObject()); EditView& rEditView2 = pView->GetTextEditOutlinerView()->GetEditView(); rEditView2.SetSelection(ESelection(0, 0, 0, 1)); // start para, start char, end para, end char. CPPUNIT_ASSERT_EQUAL( int(1411), static_cast(rEditView2.GetAttribs().Get(EE_CHAR_FONTHEIGHT).GetHeight())); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testTdf118354) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("tdf118354.odp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); CPPUNIT_ASSERT_EQUAL(static_cast(2), pActualPage->GetObjCount()); auto pTableObject = dynamic_cast(pActualPage->GetObj(0)); CPPUNIT_ASSERT(pTableObject); // Without the fix, it would crash here ::tools::Rectangle aRect = pTableObject->GetCurrentBoundRect(); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, o3tl::toTwips(aRect.Left() + 2, o3tl::Length::mm100), o3tl::toTwips(aRect.Top() + 2, o3tl::Length::mm100), 1, MOUSE_LEFT, 0); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, o3tl::toTwips(aRect.Left() + 2, o3tl::Length::mm100), o3tl::toTwips(aRect.Top() + 2, o3tl::Length::mm100), 1, MOUSE_LEFT, 0); Scheduler::ProcessEventsToIdle(); SdrView* pView = pViewShell->GetView(); auto pMarkedObj = dynamic_cast(pView->GetMarkedObjectList().GetMark(0)->GetMarkedSdrObj()); CPPUNIT_ASSERT_EQUAL(pMarkedObj, pTableObject); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testPostKeyEventInvalidation) { // Load a document and begin text edit on the first slide. SdXImpressDocument* pXImpressDocument = createDoc("2slides.odp"); CPPUNIT_ASSERT_EQUAL(0, pXImpressDocument->getPart()); ViewCallback aView1; sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdrView* pView = pViewShell->GetView(); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_F2); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_F2); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(pView->GetTextEditObject()); // Create a second view and begin text edit there as well, in parallel. SfxLokHelper::createView(); pXImpressDocument->initializeForTiledRendering({}); ViewCallback aView2; pXImpressDocument->setPart(1); sd::ViewShell* pViewShell2 = pXImpressDocument->GetDocShell()->GetViewShell(); SdrView* pView2 = pViewShell2->GetView(); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_F2); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_F2); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(pView2->GetTextEditObject()); // Now go left with the cursor in the second view and watch for // invalidations. aView2.m_bTilesInvalidated = false; pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_LEFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_LEFT); Scheduler::ProcessEventsToIdle(); // This failed: moving the cursor caused unexpected invalidation. CPPUNIT_ASSERT(!aView2.m_bTilesInvalidated); } /** * tests a cut/paste bug around bullet items in a list */ CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testTdf103083) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("tdf103083.fodp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject1 = pActualPage->GetObj(1); CPPUNIT_ASSERT_EQUAL(SdrObjKind::OutlineText, pObject1->GetObjIdentifier()); SdrTextObj* pTextObject = static_cast(pObject1); SdrView* pView = pViewShell->GetView(); // select contents of bullet item ::tools::Rectangle aRect = pTextObject->GetCurrentBoundRect(); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, o3tl::toTwips(aRect.Left() + 2, o3tl::Length::mm100), o3tl::toTwips(aRect.Top() + 2, o3tl::Length::mm100), 1, MOUSE_LEFT, 0); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, o3tl::toTwips(aRect.Left() + 2, o3tl::Length::mm100), o3tl::toTwips(aRect.Top() + 2, o3tl::Length::mm100), 1, MOUSE_LEFT, 0); Scheduler::ProcessEventsToIdle(); pView->SdrBeginTextEdit(pTextObject); CPPUNIT_ASSERT(pView->GetTextEditObject()); EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); rEditView.SetSelection(ESelection(2, 0, 2, 33)); // start para, start char, end para, end char. CPPUNIT_ASSERT_EQUAL(u"They have all the same formatting"_ustr, rEditView.GetSelected()); SdrOutliner* pOutliner = pView->GetTextEditOutliner(); CPPUNIT_ASSERT_EQUAL(u"No-Logo Content~LT~Gliederung 2"_ustr, pOutliner->GetStyleSheet(2)->GetName()); const EditTextObject& aEdit = pTextObject->GetOutlinerParaObject()->GetTextObject(); const SvxNumBulletItem* pNumFmt = aEdit.GetParaAttribs(2).GetItem(EE_PARA_NUMBULLET); SvxNumberFormat aNumFmt(pNumFmt->GetNumRule().GetLevel(2)); // cut contents of bullet item dispatchCommand(mxComponent, u".uno:Cut"_ustr, uno::Sequence()); CPPUNIT_ASSERT(pView->GetTextEditObject()); EditView& rEditView2 = pView->GetTextEditOutlinerView()->GetEditView(); rEditView2.SetSelection(ESelection(2, 0, 2, 10)); // start para, start char, end para, end char. CPPUNIT_ASSERT_EQUAL(OUString(), rEditView2.GetSelected()); // paste contents of bullet item dispatchCommand(mxComponent, u".uno:Paste"_ustr, uno::Sequence()); // send an ESC key to trigger the commit of the edit to the main model pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::ESCAPE); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::ESCAPE); Scheduler::ProcessEventsToIdle(); pView->SdrBeginTextEdit(pTextObject); CPPUNIT_ASSERT(pView->GetTextEditObject()); pOutliner = pView->GetTextEditOutliner(); EditView& rEditView3 = pView->GetTextEditOutlinerView()->GetEditView(); rEditView3.SetSelection(ESelection(2, 0, 2, 33)); // start para, start char, end para, end char. CPPUNIT_ASSERT_EQUAL(u"They have all the same formatting"_ustr, rEditView3.GetSelected()); CPPUNIT_ASSERT_EQUAL(u"No-Logo Content~LT~Gliederung 2"_ustr, pOutliner->GetStyleSheet(2)->GetName()); const EditTextObject& aEdit2 = pTextObject->GetOutlinerParaObject()->GetTextObject(); const SvxNumBulletItem* pNumFmt2 = aEdit2.GetParaAttribs(2).GetItem(EE_PARA_NUMBULLET); SvxNumberFormat aNumFmt2(pNumFmt2->GetNumRule().GetLevel(2)); bool bEqual(aNumFmt2 == aNumFmt); CPPUNIT_ASSERT_MESSAGE("Bullet properties changed after paste", bEqual); } /** * tests a clone-formatting bug around table cell attributes */ CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testTdf104405) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("tdf104405.fodp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(2); auto pTableObject = dynamic_cast(pObject); CPPUNIT_ASSERT(pTableObject); // select the middle cell SdrView* pView = pViewShell->GetView(); pView->MarkObj(pTableObject, pView->GetSdrPageView()); pTableObject->setActiveCell(sdr::table::CellPos(2,1)); pView->SdrBeginTextEdit(pTableObject); EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); rEditView.SetSelection(ESelection(0, 0, 0, 3)); // start para, start char, end para, end char. // trigger the clone-formatting/paintbrush command to copy formatting contents of cell uno::Sequence aArgs{ comphelper::makePropertyValue(u"PersistentCopy"_ustr, true) }; dispatchCommand(mxComponent, u".uno:FormatPaintbrush"_ustr, aArgs); // now click on the table pView->MarkObj(pTableObject, pView->GetSdrPageView()); pTableObject->setActiveCell(sdr::table::CellPos(0,0)); pView->SdrEndTextEdit(false); pView->SdrBeginTextEdit(pTableObject); EditView& rEditView2 = pView->GetTextEditOutlinerView()->GetEditView(); rEditView2.SetSelection(ESelection(0, 0, 0, 3)); // start para, start char, end para, end char. ::tools::Rectangle aRect = pTableObject->GetCurrentBoundRect(); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, o3tl::toTwips(aRect.Left(), o3tl::Length::mm100), o3tl::toTwips(aRect.Top(), o3tl::Length::mm100), 1, MOUSE_LEFT, 0); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, o3tl::toTwips(aRect.Left(), o3tl::Length::mm100), o3tl::toTwips(aRect.Top(), o3tl::Length::mm100), 1, MOUSE_LEFT, 0); Scheduler::ProcessEventsToIdle(); // check that the first cell has acquired the resulting vertical style xmlDocUniquePtr pXmlDoc = parseXmlDump(); // the following name has a compiler-dependent part CPPUNIT_ASSERT_EQUAL( u"2"_ustr, getXPath( pXmlDoc, "/SdDrawDocument/SdrModel/maPages/SdPage/SdrPage/SdrObjList/SdrTableObj/SdrTableObjImpl" "/TableModel/Cell[1]/DefaultProperties/SfxItemSet/SdrTextVertAdjustItem", "value")); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testTdf81754) { SdXImpressDocument* pXImpressDocument = createDoc("tdf81754.pptx"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(1); SdrTextObj* pTextObj = static_cast(pObject); SdrView* pView = pViewShell->GetView(); pView->MarkObj(pTextObj, pView->GetSdrPageView()); SfxStringItem aInputString(SID_ATTR_CHAR, u"x"_ustr); pViewShell->GetViewFrame()->GetDispatcher()->ExecuteList(SID_ATTR_CHAR, SfxCallMode::SYNCHRON, { &aInputString }); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0); Scheduler::ProcessEventsToIdle(); // now save, reload, and assert that we did not lose the edit saveAndReload(u"Impress Office Open XML"_ustr); uno::Reference xDrawPagesSupplier(mxComponent, uno::UNO_QUERY); uno::Reference xPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0), uno::UNO_QUERY); uno::Reference xShape(xPage->getByIndex(1), uno::UNO_QUERY); CPPUNIT_ASSERT_EQUAL(u"Somethingxx"_ustr, xShape->getString()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testTdf105502) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("tdf105502.odp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); sd::Window* pWindow = pViewShell->GetActiveWindow(); CPPUNIT_ASSERT(pWindow); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(0); auto pTableObject = dynamic_cast(pObject); CPPUNIT_ASSERT(pTableObject); // Select the first row. sd::View* pView = pViewShell->GetView(); pView->MarkObj(pObject, pView->GetSdrPageView()); pView->SdrBeginTextEdit(pObject); rtl::Reference xSelectionController(pView->getSelectionController()); CPPUNIT_ASSERT(xSelectionController.is()); SfxRequest aRequest(*pViewShell->GetViewFrame(), SID_TABLE_SELECT_ROW); xSelectionController->Execute(aRequest); // Assert that the A1:B1 selection succeeded. CPPUNIT_ASSERT(xSelectionController->hasSelectedCells()); sdr::table::CellPos aFirstCell; sdr::table::CellPos aLastCell; xSelectionController->getSelectedCells(aFirstCell, aLastCell); CPPUNIT_ASSERT_EQUAL(static_cast(0), aFirstCell.mnCol); CPPUNIT_ASSERT_EQUAL(static_cast(0), aFirstCell.mnRow); CPPUNIT_ASSERT_EQUAL(static_cast(1), aLastCell.mnCol); CPPUNIT_ASSERT_EQUAL(static_cast(0), aLastCell.mnRow); // Grow font size for the selection. dispatchCommand(mxComponent, u".uno:Grow"_ustr, {}); // Assert that the selected A1 has now a larger font than the unselected // A2. xmlDocUniquePtr pXmlDoc = parseXmlDump(); sal_Int32 nA1Height = getXPath(pXmlDoc, "//Cell[1]/SdrText/OutlinerParaObject/EditTextObject/ContentInfo/SfxItemSet/SvxFontHeightItem[1]", "height").toInt32(); sal_Int32 nA2Height = getXPath(pXmlDoc, "//Cell[3]/SdrText/OutlinerParaObject/EditTextObject/ContentInfo/attribs[1]/SvxFontHeightItem", "height").toInt32(); // This failed when FuText::ChangeFontSize() never did "continue" in the // text loop, instead of doing so depending on what IsInSelection() returns. CPPUNIT_ASSERT(nA1Height > nA2Height); // Check that selection remains the same CPPUNIT_ASSERT(xSelectionController->hasSelectedCells()); xSelectionController->getSelectedCells(aFirstCell, aLastCell); CPPUNIT_ASSERT_EQUAL(static_cast(0), aFirstCell.mnCol); CPPUNIT_ASSERT_EQUAL(static_cast(0), aFirstCell.mnRow); CPPUNIT_ASSERT_EQUAL(static_cast(1), aLastCell.mnCol); CPPUNIT_ASSERT_EQUAL(static_cast(0), aLastCell.mnRow); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testCommentCallbacks) { // Load the document. // Set the tiled annotations off comphelper::LibreOfficeKit::setTiledAnnotations(false); SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp", comphelper::InitPropertySequence( { {".uno:Author", uno::Any(u"LOK User1"_ustr)}, })); ViewCallback aView1; int nView1 = SfxLokHelper::getView(); SfxLokHelper::createView(); uno::Sequence aArgs(comphelper::InitPropertySequence( { {".uno:Author", uno::Any(u"LOK User2"_ustr)}, })); pXImpressDocument->initializeForTiledRendering(aArgs); ViewCallback aView2; int nView2 = SfxLokHelper::getView(); SfxLokHelper::setView(nView1); // Add a new comment aArgs = comphelper::InitPropertySequence( { {"Text", uno::Any(u"Comment"_ustr)}, }); dispatchCommand(mxComponent, u".uno:InsertAnnotation"_ustr, aArgs); // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get("action")); int nComment1 = aView1.m_aCommentCallbackResult.get("id"); CPPUNIT_ASSERT_EQUAL(nComment1, aView2.m_aCommentCallbackResult.get("id")); css::util::DateTime aDateTime; OUString aDateTimeString = OUString::createFromAscii(aView1.m_aCommentCallbackResult.get("dateTime")); CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime)); CPPUNIT_ASSERT_EQUAL(std::string("LOK User1"), aView1.m_aCommentCallbackResult.get("author")); CPPUNIT_ASSERT_EQUAL(std::string("LOK User1"), aView2.m_aCommentCallbackResult.get("author")); CPPUNIT_ASSERT_EQUAL(std::string("Comment"), aView1.m_aCommentCallbackResult.get("text")); CPPUNIT_ASSERT_EQUAL(std::string("Comment"), aView2.m_aCommentCallbackResult.get("text")); CPPUNIT_ASSERT(!aView1.m_aCommentCallbackResult.get("parthash").empty()); CPPUNIT_ASSERT(!aView2.m_aCommentCallbackResult.get("parthash").empty()); // Reply to a just added comment SfxLokHelper::setView(nView2); aArgs = comphelper::InitPropertySequence( { {"Id", uno::Any(OUString::number(nComment1))}, {"Text", uno::Any(u"Reply to comment"_ustr)}, }); dispatchCommand(mxComponent, u".uno:ReplyToAnnotation"_ustr, aArgs); // We received a LOK_CALLBACK_COMMENT callback with comment 'Modify' action CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView1.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView2.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(nComment1, aView1.m_aCommentCallbackResult.get("id")); CPPUNIT_ASSERT_EQUAL(nComment1, aView2.m_aCommentCallbackResult.get("id")); CPPUNIT_ASSERT_EQUAL(std::string("LOK User2"), aView1.m_aCommentCallbackResult.get("author")); CPPUNIT_ASSERT_EQUAL(std::string("LOK User2"), aView2.m_aCommentCallbackResult.get("author")); OUString aReplyTextView1 = OUString::createFromAscii(aView1.m_aCommentCallbackResult.get("text")); OUString aReplyTextView2 = OUString::createFromAscii(aView2.m_aCommentCallbackResult.get("text")); CPPUNIT_ASSERT(aReplyTextView1.startsWith("Reply to LOK User1")); CPPUNIT_ASSERT(aReplyTextView1.endsWith("Reply to comment")); CPPUNIT_ASSERT(aReplyTextView2.startsWith("Reply to LOK User1")); CPPUNIT_ASSERT(aReplyTextView2.endsWith("Reply to comment")); CPPUNIT_ASSERT(!aView1.m_aCommentCallbackResult.get("parthash").empty()); CPPUNIT_ASSERT(!aView2.m_aCommentCallbackResult.get("parthash").empty()); // Edit this annotation now aArgs = comphelper::InitPropertySequence( { {"Id", uno::Any(OUString::number(nComment1))}, {"Text", uno::Any(u"Edited comment"_ustr)}, }); dispatchCommand(mxComponent, u".uno:EditAnnotation"_ustr, aArgs); // We received a LOK_CALLBACK_COMMENT callback with comment 'Modify' action CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView1.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView2.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(nComment1, aView1.m_aCommentCallbackResult.get("id")); CPPUNIT_ASSERT_EQUAL(nComment1, aView2.m_aCommentCallbackResult.get("id")); CPPUNIT_ASSERT(!aView1.m_aCommentCallbackResult.get("parthash").empty()); CPPUNIT_ASSERT(!aView2.m_aCommentCallbackResult.get("parthash").empty()); CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView1.m_aCommentCallbackResult.get("text")); CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView2.m_aCommentCallbackResult.get("text")); // Delete the comment aArgs = comphelper::InitPropertySequence( { {"Id", uno::Any(OUString::number(nComment1))}, }); dispatchCommand(mxComponent, u".uno:DeleteAnnotation"_ustr, aArgs); // We received a LOK_CALLBACK_COMMENT callback with comment 'Remove' action CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView1.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView2.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(nComment1, aView1.m_aCommentCallbackResult.get("id")); CPPUNIT_ASSERT_EQUAL(nComment1, aView2.m_aCommentCallbackResult.get("id")); comphelper::LibreOfficeKit::setTiledAnnotations(true); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testCommentChangeImpress) { uno::Sequence aArgs; // Load the document. // Set the tiled annotations off comphelper::LibreOfficeKit::setTiledAnnotations(false); createDoc("dummy.odp", comphelper::InitPropertySequence( { {".uno:Author", uno::Any(u"LOK User1"_ustr)}, })); ViewCallback aView1; // Add a new comment aArgs = comphelper::InitPropertySequence( { {"Text", uno::Any(u"Comment"_ustr)}, }); dispatchCommand(mxComponent, u".uno:InsertAnnotation"_ustr, aArgs); CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get("action")); int nComment1 = aView1.m_aCommentCallbackResult.get("id"); CPPUNIT_ASSERT(!aView1.m_aCommentCallbackResult.get("parthash").empty()); CPPUNIT_ASSERT_EQUAL(std::string("Comment"), aView1.m_aCommentCallbackResult.get("text")); CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 478, 280"), aView1.m_aCommentCallbackResult.get("rectangle")); // Edit this annotation now aArgs = comphelper::InitPropertySequence( { {"Id", uno::Any(OUString::number(nComment1))}, {"PositionX", uno::Any(sal_Int32(10))}, {"PositionY", uno::Any(sal_Int32(20))} }); dispatchCommand(mxComponent, u".uno:EditAnnotation"_ustr, aArgs); CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView1.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(std::string("Comment"), aView1.m_aCommentCallbackResult.get("text")); CPPUNIT_ASSERT_EQUAL(std::string("10, 20, 478, 280"), aView1.m_aCommentCallbackResult.get("rectangle")); comphelper::LibreOfficeKit::setTiledAnnotations(true); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testCommentChangeDraw) { uno::Sequence aArgs; // Load the document. // Set the tiled annotations off comphelper::LibreOfficeKit::setTiledAnnotations(false); createDoc("dummy.odg", comphelper::InitPropertySequence( { {".uno:Author", uno::Any(u"LOK User1"_ustr)}, })); ViewCallback aView1; // Add a new comment aArgs = comphelper::InitPropertySequence( { {"Text", uno::Any(u"Comment"_ustr)}, }); dispatchCommand(mxComponent, u".uno:InsertAnnotation"_ustr, aArgs); CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get("action")); int nComment1 = aView1.m_aCommentCallbackResult.get("id"); CPPUNIT_ASSERT(!aView1.m_aCommentCallbackResult.get("parthash").empty()); CPPUNIT_ASSERT_EQUAL(std::string("Comment"), aView1.m_aCommentCallbackResult.get("text")); CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 478, 280"), aView1.m_aCommentCallbackResult.get("rectangle")); // Edit this annotation now aArgs = comphelper::InitPropertySequence( { {"Id", uno::Any(OUString::number(nComment1))}, {"PositionX", uno::Any(sal_Int32(10))}, {"PositionY", uno::Any(sal_Int32(20))} }); dispatchCommand(mxComponent, u".uno:EditAnnotation"_ustr, aArgs); CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView1.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(std::string("Comment"), aView1.m_aCommentCallbackResult.get("text")); CPPUNIT_ASSERT_EQUAL(std::string("10, 20, 478, 280"), aView1.m_aCommentCallbackResult.get("rectangle")); comphelper::LibreOfficeKit::setTiledAnnotations(true); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testMultiViewInsertDeletePage) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); ViewCallback aView1; int nView1 = SfxLokHelper::getView(); uno::Sequence aArgs; SdDrawDocument* pDoc = pXImpressDocument->GetDocShell()->GetDoc(); // Create second view SfxLokHelper::createView(); pXImpressDocument->initializeForTiledRendering(aArgs); ViewCallback aView2; int nView2 = SfxLokHelper::getView(); // the document has 8 slides CPPUNIT_ASSERT_EQUAL(static_cast(8), pDoc->GetSdPageCount(PageKind::Standard)); // Switch to 5th page in 2nd view pXImpressDocument->setPart(4); // Insert slide in 1st view SfxLokHelper::setView(nView1); dispatchCommand(mxComponent, u".uno:InsertPage"_ustr, aArgs); // See if the current slide number changed in 2nd view too SfxLokHelper::setView(nView2); CPPUNIT_ASSERT_EQUAL(5, pXImpressDocument->getPart()); // Delete the page in 1st view now SfxLokHelper::setView(nView1); dispatchCommand(mxComponent, u".uno:DeletePage"_ustr, aArgs); // See if current slide number changed in 2nd view too SfxLokHelper::setView(nView2); CPPUNIT_ASSERT_EQUAL(4, pXImpressDocument->getPart()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testMultiViewInsertDeletePage2) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); ViewCallback aView1; int nView1 = SfxLokHelper::getView(); uno::Sequence aArgs; SdDrawDocument* pDoc = pXImpressDocument->GetDocShell()->GetDoc(); // Create second view SfxLokHelper::createView(); pXImpressDocument->initializeForTiledRendering(aArgs); ViewCallback aView2; int nView2 = SfxLokHelper::getView(); // the document has 8 slides CPPUNIT_ASSERT_EQUAL(static_cast(8), pDoc->GetSdPageCount(PageKind::Standard)); // Switch to 5th page in 2nd view pXImpressDocument->setPart(4); // Begin text edit on the only object on the slide. sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject1 = pActualPage->GetObj(0); CPPUNIT_ASSERT(pObject1 != nullptr); CPPUNIT_ASSERT_EQUAL(SdrObjKind::TitleText, pObject1->GetObjIdentifier()); SdrTextObj* pTextObject = static_cast(pObject1); // Double-click outside the text to enter edit mode. const ::tools::Rectangle aRect = pTextObject->GetCurrentBoundRect(); const auto cornerX = o3tl::toTwips(aRect.Left() + (aRect.getOpenWidth() / 4), o3tl::Length::mm100); const auto cornerY = o3tl::toTwips(aRect.Top() + (aRect.getOpenHeight() / 4), o3tl::Length::mm100); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, cornerX, cornerY, 2, MOUSE_LEFT, 0); pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, cornerX, cornerY, 2, MOUSE_LEFT, 0); Scheduler::ProcessEventsToIdle(); // We must be in text editing mode and have cursor visible. CPPUNIT_ASSERT(pViewShell->GetView()->IsTextEdit()); // Insert slide in 1st view SfxLokHelper::setView(nView1); dispatchCommand(mxComponent, u".uno:InsertPage"_ustr, aArgs); // See if the current slide number changed in 2nd view too SfxLokHelper::setView(nView2); CPPUNIT_ASSERT_EQUAL(5, pXImpressDocument->getPart()); // Delete the page in 1st view now SfxLokHelper::setView(nView1); dispatchCommand(mxComponent, u".uno:DeletePage"_ustr, aArgs); // See if current slide number changed in 2nd view too SfxLokHelper::setView(nView2); CPPUNIT_ASSERT_EQUAL(4, pXImpressDocument->getPart()); // We must be still in text editing mode and have cursor visible. CPPUNIT_ASSERT(pViewShell->GetView()->IsTextEdit()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testDisableUndoRepair) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); // Create View 1 SfxViewShell* pView1 = SfxViewShell::Current(); sd::ViewShell* pViewShell1 = pXImpressDocument->GetDocShell()->GetViewShell(); int nView1 = SfxLokHelper::getView(); // Create View 2 SfxLokHelper::createView(); SfxViewShell* pView2 = SfxViewShell::Current(); sd::ViewShell* pViewShell2 = pXImpressDocument->GetDocShell()->GetViewShell(); int nView2 = SfxLokHelper::getView(); // Check UNDO is disabled { std::unique_ptr pItem1; std::unique_ptr pItem2; CPPUNIT_ASSERT_EQUAL(SfxItemState::DISABLED, pView1->GetViewFrame().GetBindings().QueryState(SID_UNDO, pItem1)); CPPUNIT_ASSERT_EQUAL(SfxItemState::DISABLED, pView2->GetViewFrame().GetBindings().QueryState(SID_UNDO, pItem2)); } // Insert a character in the first view. SfxLokHelper::setView(nView1); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'h', 0); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'h', 0); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(pViewShell1->GetView()->IsTextEdit()); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::ESCAPE); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::ESCAPE); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(!pViewShell1->GetView()->IsTextEdit()); // Check { std::unique_ptr xItem1; pView1->GetViewFrame().GetBindings().QueryState(SID_UNDO, xItem1); const auto* pUInt32Item1 = dynamic_cast(xItem1.get()); CPPUNIT_ASSERT(!pUInt32Item1); std::unique_ptr xItem2; pView2->GetViewFrame().GetBindings().QueryState(SID_UNDO, xItem2); const auto* pUInt32Item2 = dynamic_cast(xItem2.get()); CPPUNIT_ASSERT(pUInt32Item2); CPPUNIT_ASSERT_EQUAL(static_cast(SID_REPAIRPACKAGE), pUInt32Item2->GetValue()); } // Insert a character in the second view. SfxLokHelper::setView(nView2); pXImpressDocument->setPart(1); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'c', 0); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'c', 0); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(pViewShell2->GetView()->IsTextEdit()); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::ESCAPE); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::ESCAPE); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(!pViewShell2->GetView()->IsTextEdit()); // Check { std::unique_ptr xItem1; pView1->GetViewFrame().GetBindings().QueryState(SID_UNDO, xItem1); const SfxUInt32Item* pUInt32Item = dynamic_cast(xItem1.get()); CPPUNIT_ASSERT(pUInt32Item); CPPUNIT_ASSERT_EQUAL(static_cast(SID_REPAIRPACKAGE), pUInt32Item->GetValue()); std::unique_ptr xItem2; pView2->GetViewFrame().GetBindings().QueryState(SID_UNDO, xItem2); CPPUNIT_ASSERT(!dynamic_cast< const SfxUInt32Item* >(xItem2.get())); } } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testDocumentRepair) { // Create two views. SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); CPPUNIT_ASSERT(pXImpressDocument); // view #1 SfxViewShell* pView1 = SfxViewShell::Current(); // view #2 SfxLokHelper::createView(); SfxViewShell* pView2 = SfxViewShell::Current(); int nView2 = SfxLokHelper::getView(); sd::ViewShell* pViewShell2 = pXImpressDocument->GetDocShell()->GetViewShell(); CPPUNIT_ASSERT(pView1 != pView2); { std::unique_ptr pItem1; pView1->GetViewFrame().GetBindings().QueryState(SID_DOC_REPAIR, pItem1); CPPUNIT_ASSERT(pItem1); CPPUNIT_ASSERT_EQUAL(false, pItem1->GetValue()); std::unique_ptr pItem2; pView2->GetViewFrame().GetBindings().QueryState(SID_DOC_REPAIR, pItem2); CPPUNIT_ASSERT(pItem2); CPPUNIT_ASSERT_EQUAL(false, pItem2->GetValue()); } // Insert a character in the second view. SfxLokHelper::setView(nView2); pXImpressDocument->setPart(1); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'c', 0); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'c', 0); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(pViewShell2->GetView()->IsTextEdit()); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::ESCAPE); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::ESCAPE); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(!pViewShell2->GetView()->IsTextEdit()); { std::unique_ptr pItem1; pView1->GetViewFrame().GetBindings().QueryState(SID_DOC_REPAIR, pItem1); CPPUNIT_ASSERT(pItem1); CPPUNIT_ASSERT_EQUAL(true, pItem1->GetValue()); std::unique_ptr pItem2; pView2->GetViewFrame().GetBindings().QueryState(SID_DOC_REPAIR, pItem2); CPPUNIT_ASSERT(pItem2); CPPUNIT_ASSERT_EQUAL(true, pItem2->GetValue()); } } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testLanguageStatus) { // Load the document. createDoc("dummy.odp"); SfxViewShell* pView1 = SfxViewShell::Current(); SfxLokHelper::createView(); SfxViewShell* pView2 = SfxViewShell::Current(); { std::unique_ptr xItem1; std::unique_ptr xItem2; pView1->GetViewFrame().GetBindings().QueryState(SID_LANGUAGE_STATUS, xItem1); pView2->GetViewFrame().GetBindings().QueryState(SID_LANGUAGE_STATUS, xItem2); auto pStringItem = dynamic_cast(xItem1.get()); CPPUNIT_ASSERT(pStringItem); CPPUNIT_ASSERT_EQUAL(u"English (USA);en-US"_ustr, pStringItem->GetValue()); CPPUNIT_ASSERT(dynamic_cast< const SfxStringItem* >(xItem2.get())); } } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testLanguageAllText) { // Load the document, which has a single shape, with Hungarian text. createDoc("language-all-text.odp"); // Set the language to English for all text. uno::Sequence aArgs = comphelper::InitPropertySequence({ { "Language", uno::Any(u"Default_English (USA)"_ustr) }, }); dispatchCommand(mxComponent, u".uno:LanguageStatus"_ustr, aArgs); // Assert that the shape text language was changed. uno::Reference xDrawPagesSupplier(mxComponent, uno::UNO_QUERY); uno::Reference xPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0), uno::UNO_QUERY); uno::Reference xShape(xPage->getByIndex(0), uno::UNO_QUERY); uno::Reference xRun(xShape, uno::UNO_QUERY_THROW); lang::Locale aLocale; xRun->getPropertyValue(u"CharLocale"_ustr) >>= aLocale; // Without the accompanying fix in place, this test would have failed with 'Expected: en; // Actual: hu', as the shape text language was not set. CPPUNIT_ASSERT_EQUAL(u"en"_ustr, aLocale.Language); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testDefaultView) { // Load the document with notes view. SdXImpressDocument* pXImpressDocument = createDoc("notes-view.odp"); sd::ViewShell* pView = pXImpressDocument->GetDocShell()->GetViewShell(); { std::unique_ptr pImpressView; std::unique_ptr pNotesView; pView->GetViewFrame()->GetBindings().QueryState(SID_NORMAL_MULTI_PANE_GUI, pImpressView); pView->GetViewFrame()->GetBindings().QueryState(SID_NOTES_MODE, pNotesView); CPPUNIT_ASSERT(pImpressView); CPPUNIT_ASSERT(pNotesView); CPPUNIT_ASSERT_EQUAL(true, pImpressView->GetValue()); CPPUNIT_ASSERT_EQUAL(false, pNotesView->GetValue()); } } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testIMESupport) { // Load the document with notes view. SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); VclPtr pDocWindow = pXImpressDocument->getDocWindow(); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdrObject* pObject = pViewShell->GetActualPage()->GetObj(0); SdrTextObj* pTextObj = static_cast(pObject); SdrView* pView = pViewShell->GetView(); pView->MarkObj(pTextObj, pView->GetSdrPageView()); SfxStringItem aInputString(SID_ATTR_CHAR, u"x"_ustr); pViewShell->GetViewFrame()->GetDispatcher()->ExecuteList(SID_ATTR_CHAR, SfxCallMode::SYNCHRON, { &aInputString }); // sequence of chinese IME compositions when 'nihao' is typed in an IME const std::vector aUtf8Inputs{ "年"_ostr, "你"_ostr, "你好"_ostr, "你哈"_ostr, "你好"_ostr, "你好"_ostr }; std::vector 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, u""_ustr); // the cursor should be at position 3rd EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); CPPUNIT_ASSERT_EQUAL(static_cast(3), rEditView.GetSelection().start.nIndex); ESelection aWordSelection(0, 0, 0, 3); // start para, start char, end para, end char. rEditView.SetSelection(aWordSelection); // content contains only the last IME composition, not all CPPUNIT_ASSERT_EQUAL(OUString("x" + aInputs[aInputs.size() - 1]), rEditView.GetSelected()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testTdf115783) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("tdf115783.fodp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(0); auto pTableObject = dynamic_cast(pObject); CPPUNIT_ASSERT(pTableObject); SdrView* pView = pViewShell->GetView(); pView->MarkObj(pTableObject, pView->GetSdrPageView()); // Create a cell selection and set font height. // Go to the end of the B1 cell. pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_LEFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_LEFT); // Create a B1->C1 cell selection. const int nShiftRight = KEY_SHIFT + KEY_RIGHT; pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, nShiftRight); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, nShiftRight); uno::Sequence aArgs = comphelper::InitPropertySequence({ { "FontHeight.Height", uno::Any(static_cast(12)) }, }); dispatchCommand(mxComponent, u".uno:FontHeight"_ustr, aArgs); // Create a text selection on the B1 cell. pTableObject->setActiveCell(sdr::table::CellPos(1, 0)); pView->SdrBeginTextEdit(pTableObject); EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); // Start para, start char, end para, end char. rEditView.SetSelection(ESelection(0, 0, 0, 5)); CPPUNIT_ASSERT_EQUAL(u"hello"_ustr, rEditView.GetSelected()); // Copy selection, paste at the start of the cell. aArgs = {}; dispatchCommand(mxComponent, u".uno:Copy"_ustr, aArgs); rEditView.SetSelection(ESelection(0, 0, 0, 0)); aArgs = {}; dispatchCommand(mxComponent, u".uno:Paste"_ustr, aArgs); pView->SdrEndTextEdit(); // And now verify that the cell has the correct font size. uno::Reference xTable = pTableObject->getTable(); CPPUNIT_ASSERT(xTable.is()); uno::Reference xCell(xTable->getCellByPosition(1, 0), uno::UNO_QUERY); CPPUNIT_ASSERT(xCell.is()); uno::Reference xText(xCell->getText(), uno::UNO_QUERY); CPPUNIT_ASSERT(xText.is()); uno::Reference xParagraph( xText->createEnumeration()->nextElement(), uno::UNO_QUERY); CPPUNIT_ASSERT(xParagraph.is()); uno::Reference xPortion(xParagraph->createEnumeration()->nextElement(), uno::UNO_QUERY); CPPUNIT_ASSERT(xPortion.is()); // This failed, it was only "hello" as the paragraph had 2 portions: a // "hello" with 12pt size and a "hello" with 18pt. CPPUNIT_ASSERT_EQUAL(u"hellohello"_ustr, xPortion->getString()); uno::Reference xPropertySet(xPortion, uno::UNO_QUERY); int nHeight = xPropertySet->getPropertyValue(u"CharHeight"_ustr).get(); // Make sure that the single font size for the cell is the expected one. CPPUNIT_ASSERT_EQUAL(12, nHeight); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testPasteTextOnSlide) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("paste_text_onslide.odp"); CPPUNIT_ASSERT(pXImpressDocument); // select second text object pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::TAB); Scheduler::ProcessEventsToIdle(); // step into text editing pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, '1', 0); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, '1', 0); Scheduler::ProcessEventsToIdle(); // select full text pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_LEFT | KEY_SHIFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_LEFT | KEY_SHIFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_LEFT | KEY_SHIFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_LEFT | KEY_SHIFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_LEFT | KEY_SHIFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_LEFT | KEY_SHIFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_LEFT | KEY_SHIFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_LEFT | KEY_SHIFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_LEFT | KEY_SHIFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_LEFT | KEY_SHIFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_LEFT | KEY_SHIFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_LEFT | KEY_SHIFT); Scheduler::ProcessEventsToIdle(); // Copy some text dispatchCommand(mxComponent, u".uno:Copy"_ustr, uno::Sequence()); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::ESCAPE); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::ESCAPE); Scheduler::ProcessEventsToIdle(); // Paste onto the slide dispatchCommand(mxComponent, u".uno:Paste"_ustr, uno::Sequence()); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::ESCAPE); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::ESCAPE); Scheduler::ProcessEventsToIdle(); // Check the position of the newly added text shape, created for pasted text SdPage* pActualPage = pXImpressDocument->GetDocShell()->GetViewShell()->GetActualPage(); CPPUNIT_ASSERT_EQUAL(static_cast(3), pActualPage->GetObjCount()); SdrObject* pObject = pActualPage->GetObj(2); CPPUNIT_ASSERT(pObject); SdrTextObj* pTextObj = DynCastSdrTextObj(pObject); CPPUNIT_ASSERT(pTextObj); CPPUNIT_ASSERT_EQUAL(SdrObjKind::Text, pTextObj->GetObjIdentifier()); const Point aPos = pTextObj->GetLastBoundRect().TopLeft(); CPPUNIT_ASSERT_EQUAL(static_cast(0), aPos.getX()); CPPUNIT_ASSERT_EQUAL(static_cast(0), aPos.getY()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testTdf115873) { // Initialize the navigator. SdXImpressDocument* pXImpressDocument = createDoc("tdf115873.fodp"); SfxViewShell* pViewShell = SfxViewShell::Current(); CPPUNIT_ASSERT(pViewShell); SfxBindings& rBindings = pViewShell->GetViewFrame().GetBindings(); auto xNavigator = std::make_unique(nullptr, &rBindings, nullptr); xNavigator->InitTreeLB(pXImpressDocument->GetDoc()); SdPageObjsTLV& rObjects = xNavigator->GetObjects(); rObjects.SelectEntry(u"Slide 1"); rObjects.Select(); sd::ViewShell* pSdViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdrView* pSdrView = pSdViewShell->GetView(); pSdrView->UnmarkAllObj(pSdrView->GetSdrPageView()); // Make sure that no shapes are selected. const SdrMarkList& rMarkList = pSdrView->GetMarkedObjectList(); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(static_cast(0), rMarkList.GetMarkCount()); // Single-click with the mouse. MouseEvent aMouseEvent(Point(0, 0), /*nClicks=*/1, MouseEventModifiers::NONE, MOUSE_LEFT); rObjects.MousePressHdl(aMouseEvent); rObjects.SelectEntry(u"Rectangle"); rObjects.Select(); rObjects.MouseReleaseHdl(aMouseEvent); Scheduler::ProcessEventsToIdle(); // This failed, single-click did not result in a shape selection (only // double-click did). CPPUNIT_ASSERT_EQUAL(static_cast(1), rMarkList.GetMarkCount()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testTdf115873Group) { // Initialize the navigator. SdXImpressDocument* pXImpressDocument = createDoc("tdf115873-group.fodp"); SfxViewShell* pViewShell = SfxViewShell::Current(); CPPUNIT_ASSERT(pViewShell); SfxBindings& rBindings = pViewShell->GetViewFrame().GetBindings(); auto xNavigator = std::make_unique(nullptr, &rBindings, nullptr); xNavigator->InitTreeLB(pXImpressDocument->GetDoc()); SdPageObjsTLV& rObjects = xNavigator->GetObjects(); // This failed, Fill() and IsEqualToDoc() were out of sync for group // shapes. CPPUNIT_ASSERT(rObjects.IsEqualToDoc(pXImpressDocument->GetDoc())); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testCutSelectionChange) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("cut_selection_change.odp"); CPPUNIT_ASSERT(pXImpressDocument); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); setupLibreOfficeKitViewCallback(pViewShell->GetViewShellBase()); Scheduler::ProcessEventsToIdle(); // Select first text object pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::TAB); Scheduler::ProcessEventsToIdle(); // step into text editing pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, '1', 0); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, '1', 0); Scheduler::ProcessEventsToIdle(); // select some text pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_LEFT | KEY_SHIFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_LEFT | KEY_SHIFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_LEFT | KEY_SHIFT); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_LEFT | KEY_SHIFT); Scheduler::ProcessEventsToIdle(); // Check that we have a selection before cutting CPPUNIT_ASSERT_EQUAL(static_cast(1), m_aSelection.size()); // Cut the selected text dispatchCommand(mxComponent, u".uno:Cut"_ustr, uno::Sequence()); // Selection is removed CPPUNIT_ASSERT_EQUAL(static_cast(0), m_aSelection.size()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testGetViewRenderState) { // Add a pair of schemes, last added is the default svtools::EditableColorConfig aColorConfig; aColorConfig.AddScheme(u"Dark"_ustr); aColorConfig.AddScheme(u"Light"_ustr); SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); int nFirstViewId = SfxLokHelper::getView(); ViewCallback aView1; CPPUNIT_ASSERT_EQUAL("S;Default"_ostr, pXImpressDocument->getViewRenderState()); // Create a second view SfxLokHelper::createView(); ViewCallback aView2; CPPUNIT_ASSERT_EQUAL("S;Default"_ostr, pXImpressDocument->getViewRenderState()); // Set to dark scheme { uno::Sequence aPropertyValues = comphelper::InitPropertySequence( { { "NewTheme", uno::Any(u"Dark"_ustr) }, } ); dispatchCommand(mxComponent, u".uno:ChangeTheme"_ustr, aPropertyValues); } CPPUNIT_ASSERT_EQUAL("S;Dark"_ostr, pXImpressDocument->getViewRenderState()); // Switch back to the first view, and check that the options string is the same SfxLokHelper::setView(nFirstViewId); CPPUNIT_ASSERT_EQUAL("S;Default"_ostr, pXImpressDocument->getViewRenderState()); } // Helper function to get a tile to a bitmap and check the pixel color static void assertTilePixelColor(SdXImpressDocument* pXImpressDocument, int nPixelX, int nPixelY, Color aColor) { size_t nCanvasSize = 1024; size_t nTileSize = 256; std::vector aPixmap(nCanvasSize * nCanvasSize * 4, 0); ScopedVclPtrInstance pDevice(DeviceFormat::WITHOUT_ALPHA); pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasSize, nCanvasSize), Fraction(1.0), Point(), aPixmap.data()); pXImpressDocument->paintTile(*pDevice, nCanvasSize, nCanvasSize, 0, 0, 15360, 7680); pDevice->EnableMapMode(false); Bitmap aBitmap = pDevice->GetBitmap(Point(0, 0), Size(nTileSize, nTileSize)); BitmapScopedReadAccess pAccess(aBitmap); Color aActualColor(pAccess->GetPixel(nPixelX, nPixelY)); CPPUNIT_ASSERT_EQUAL(aColor, aActualColor); } // Test that changing the theme in one view doesn't change it in the other view CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testThemeViewSeparation) { Color aDarkColor(0x1c, 0x1c, 0x1c); // Add a minimal dark scheme { svtools::EditableColorConfig aColorConfig; svtools::ColorConfigValue aValue; aValue.bIsVisible = true; // aValue.nColor is just used for caching, so we need to set nDarkColor or nLightColor to specify the light/dark // color values for an element. Which one will be used is decided based on the application's appearance setting. // see commit message of Change-Id: I1a7f70dfe44b81f863814f87e8d46e146c0e3d5a aValue.nDarkColor = aDarkColor; aValue.nLightColor = aDarkColor; 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.nLightColor = COL_WHITE; aValue.nDarkColor = COL_WHITE; aColorConfig.SetColorValue(svtools::DOCCOLOR, aValue); aColorConfig.AddScheme(u"Light"_ustr); } SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); int nFirstViewId = SfxLokHelper::getView(); ViewCallback aView1; // Switch first view to light scheme { uno::Sequence aPropertyValues = comphelper::InitPropertySequence( { { "NewTheme", uno::Any(u"Light"_ustr) }, } ); dispatchCommand(mxComponent, u".uno:ChangeTheme"_ustr, aPropertyValues); } // First view is at light scheme assertTilePixelColor(pXImpressDocument, 255, 255, COL_WHITE); // Create second view SfxLokHelper::createView(); int nSecondViewId = SfxLokHelper::getView(); ViewCallback aView2; // Set second view to dark scheme { uno::Sequence aPropertyValues = comphelper::InitPropertySequence( { { "NewTheme", uno::Any(u"Dark"_ustr) }, } ); dispatchCommand(mxComponent, u".uno:ChangeTheme"_ustr, aPropertyValues); } assertTilePixelColor(pXImpressDocument, 255, 255, aDarkColor); // First view still in light scheme SfxLokHelper::setView(nFirstViewId); assertTilePixelColor(pXImpressDocument, 255, 255, COL_WHITE); // Second view still in dark scheme SfxLokHelper::setView(nSecondViewId); assertTilePixelColor(pXImpressDocument, 255, 255, Color(0x1c, 0x1c, 0x1c)); // Switch second view back to light scheme { uno::Sequence aPropertyValues = comphelper::InitPropertySequence( { { "NewTheme", uno::Any(u"Light"_ustr) }, } ); dispatchCommand(mxComponent, u".uno:ChangeTheme"_ustr, aPropertyValues); } // Now in light scheme assertTilePixelColor(pXImpressDocument, 255, 255, COL_WHITE); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testRegenerateDiagram) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("regenerate-diagram.pptx"); CPPUNIT_ASSERT(pXImpressDocument); SdPage* pActualPage = pXImpressDocument->GetDocShell()->GetViewShell()->GetActualPage(); CPPUNIT_ASSERT_EQUAL(static_cast(4), pActualPage->GetObj(0)->GetSubList()->GetObjCount()); // For new Diagram functionality entering group using UI is not allowed as long // as the group shape is a diagram. Do the same as before done by triggering UI // events directly in the model // Remove and free top-left entry (Box showing "A") pActualPage->GetObj(0)->GetSubList()->RemoveObject(1); // select diagram pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::TAB); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::TAB); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(static_cast(3), pActualPage->GetObj(0)->GetSubList()->GetObjCount()); // regenerate diagram dispatchCommand(mxComponent, u".uno:RegenerateDiagram"_ustr, uno::Sequence()); // diagram content (child shape count) should be the same as in the beginning CPPUNIT_ASSERT_EQUAL(static_cast(4), pActualPage->GetObj(0)->GetSubList()->GetObjCount()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testInsertDeletePageInvalidation) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); ViewCallback aView1; CPPUNIT_ASSERT_EQUAL(8, pXImpressDocument->getParts()); // Insert slide aView1.m_bTilesInvalidated = false; aView1.m_aInvalidations.clear(); dispatchCommand(mxComponent, u".uno:InsertPage"_ustr, uno::Sequence()); CPPUNIT_ASSERT(aView1.m_bTilesInvalidated); CPPUNIT_ASSERT_EQUAL(9, pXImpressDocument->getParts()); CPPUNIT_ASSERT_EQUAL(size_t(9), aView1.m_aInvalidations.size()); // Delete slide aView1.m_bTilesInvalidated = false; aView1.m_aInvalidations.clear(); dispatchCommand(mxComponent, u".uno:DeletePage"_ustr, uno::Sequence()); CPPUNIT_ASSERT(aView1.m_bTilesInvalidated); CPPUNIT_ASSERT_EQUAL(8, pXImpressDocument->getParts()); CPPUNIT_ASSERT_EQUAL(size_t(8), aView1.m_aInvalidations.size()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSpellOnlineRenderParameter) { // Load the document. SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); bool bSet = pXImpressDocument->GetDoc()->GetOnlineSpell(); uno::Sequence aPropertyValues = { comphelper::InitPropertySequence({ { ".uno:SpellOnline", uno::Any(!bSet) } }), }; pXImpressDocument->initializeForTiledRendering(aPropertyValues); CPPUNIT_ASSERT_EQUAL(!bSet, pXImpressDocument->GetDoc()->GetOnlineSpell()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSlideDuplicateUndo) { // Create two views. SdXImpressDocument* pXImpressDocument = createDoc("duplicate-undo.odp"); int nView0 = SfxLokHelper::getView(); SfxLokHelper::createView(); pXImpressDocument->initializeForTiledRendering({}); int nView1 = SfxLokHelper::getView(); SfxLokHelper::setView(nView0); // Switch to the 3rd slide on view 0, and start text editing. { pXImpressDocument->setPart(2); sd::ViewShell* pViewShell0 = pXImpressDocument->GetDocShell()->GetViewShell(); SdrView* pView = pViewShell0->GetView(); SdPage* pActualPage = pViewShell0->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(1); SdrTextObj* pTextObj = static_cast(pObject); pView->MarkObj(pTextObj, pView->GetSdrPageView()); SfxStringItem aInputString(SID_ATTR_CHAR, u"x"_ustr); pViewShell0->GetViewFrame()->GetDispatcher()->ExecuteList(SID_ATTR_CHAR, SfxCallMode::SYNCHRON, { &aInputString }); CPPUNIT_ASSERT(pView->IsTextEdit()); CPPUNIT_ASSERT(pView->GetTextEditPageView()); } // Duplicate the first slide on view 1 and undo it. SfxLokHelper::setView(nView1); dispatchCommand(mxComponent, u".uno:DuplicatePage"_ustr, {}); pXImpressDocument->setPart(0, /*bAllowChangeFocus=*/false); pXImpressDocument->setPart(1, /*bAllowChangeFocus=*/false); SfxLokHelper::setView(nView0); pXImpressDocument->setPart(0, /*bAllowChangeFocus=*/false); pXImpressDocument->setPart(3, /*bAllowChangeFocus=*/false); SfxLokHelper::setView(nView1); pXImpressDocument->getUndoManager()->undo(); // Without the accompanying fix in place, this would have tried to access the outdated page view // pointer, potentially leading to a crash. pXImpressDocument->setPart(2, /*bAllowChangeFocus=*/false); // Make sure that view 0 now doesn't have an outdated page view pointer. SfxLokHelper::setView(nView0); sd::ViewShell* pViewShell0 = pXImpressDocument->GetDocShell()->GetViewShell(); SdrView* pView0 = pViewShell0->GetView(); CPPUNIT_ASSERT(!pView0->GetTextEditPageView()); } namespace { void lcl_extractHandleParameters(std::string_view selection, sal_uInt32& id, sal_uInt32& x, sal_uInt32& y) { OString extraInfo( selection.substr(selection.find("{")) ); std::stringstream aStream((std::string(extraInfo))); boost::property_tree::ptree aTree; boost::property_tree::read_json(aStream, aTree); boost::property_tree::ptree handle0 = aTree .get_child("handles") .get_child("kinds") .get_child("rectangle") .get_child("1") .begin()->second; id = handle0.get_child("id").get_value(); x = handle0.get_child("point").get_child("x").get_value(); y = handle0.get_child("point").get_child("y").get_value(); CPPUNIT_ASSERT(aTree.get_child("OrdNum").get_value() >= 0); CPPUNIT_ASSERT(aTree.get_child("ObjectRectangles").size() >= 1); } } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testMoveShapeHandle) { SdXImpressDocument* pXImpressDocument = createDoc("shape.odp"); ViewCallback aView1; sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pPage = pViewShell->GetActualPage(); SdrObject* pObject = pPage->GetObj(0); SdrView* pView = pViewShell->GetView(); pView->MarkObj(pObject, pView->GetSdrPageView()); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(!aView1.m_ShapeSelection.isEmpty()); { sal_uInt32 id, x, y; lcl_extractHandleParameters(aView1.m_ShapeSelection, id, x ,y); sal_uInt32 oldX = x; sal_uInt32 oldY = y; uno::Sequence aPropertyValues(comphelper::InitPropertySequence( { {"HandleNum", uno::Any(id)}, {"NewPosX", uno::Any(x+1)}, {"NewPosY", uno::Any(y+1)} })); dispatchCommand(mxComponent, u".uno:MoveShapeHandle"_ustr, aPropertyValues); CPPUNIT_ASSERT(!aView1.m_ShapeSelection.isEmpty()); lcl_extractHandleParameters(aView1.m_ShapeSelection, id, x ,y); CPPUNIT_ASSERT_EQUAL(x-1, oldX); CPPUNIT_ASSERT_EQUAL(y-1, oldY); } } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testPasteUndo) { // Given a document with a textbox, containing "world": SdXImpressDocument* pXImpressDocument = createDoc("paste-undo.fodp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); SdPage* pActualPage = pViewShell->GetActualPage(); SdrObject* pObject = pActualPage->GetObj(0); SdrView* pView = pViewShell->GetView(); pView->MarkObj(pObject, pView->GetSdrPageView()); pView->SdrBeginTextEdit(pObject); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_HOME); pXImpressDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_HOME); EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); ESelection aWordSelection(0, 0, 0, 1); // "w" of "world" rEditView.SetSelection(aWordSelection); dispatchCommand(mxComponent, u".uno:Cut"_ustr, {}); // When undoing a paste: dispatchCommand(mxComponent, u".uno:Paste"_ustr, {}); dispatchCommand(mxComponent, u".uno:Undo"_ustr, {}); // Then make sure the cursor position is still at the beginning: ESelection aSelection = rEditView.GetSelection(); // Without the accompanying fix in place, this test would have failed with: // - Expected: 0 // - Actual : 4 // i.e. the cursor position after undo was at the end of the line, not at the start, as // expected. CPPUNIT_ASSERT_EQUAL(static_cast(0), aSelection.start.nIndex); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testShapeEditInMultipleViews) { SdXImpressDocument* pXImpressDocument = createDoc("TextBoxAndRect.odg"); pXImpressDocument->initializeForTiledRendering(uno::Sequence()); SdDrawDocument* pDocument = pXImpressDocument->GetDoc(); // Create view 1 const int nView1 = SfxLokHelper::getView(); sd::ViewShell* pViewShell1 = pXImpressDocument->GetDocShell()->GetViewShell(); SdrView* pView1 = pViewShell1->GetView(); Scheduler::ProcessEventsToIdle(); // Create view 2 SfxLokHelper::createView(); const int nView2 = SfxLokHelper::getView(); CPPUNIT_ASSERT(nView1 != nView2); sd::ViewShell* pViewShell2 = pXImpressDocument->GetDocShell()->GetViewShell(); SdrView* pView2 = pViewShell2->GetView(); Scheduler::ProcessEventsToIdle(); // Switch to view 1 SfxLokHelper::setView(nView1); SdPage* pPage1 = pViewShell1->GetActualPage(); SdrObject* pTextBoxObject = pPage1->GetObj(0); CPPUNIT_ASSERT_EQUAL(u"Text Box"_ustr, pTextBoxObject->GetName()); SdrObject* pRectangleObject = pPage1->GetObj(1); CPPUNIT_ASSERT_EQUAL(u"Rect"_ustr, pRectangleObject->GetName()); SdrObject* pTableObject = pPage1->GetObj(2); CPPUNIT_ASSERT_EQUAL(u"Table1"_ustr, pTableObject->GetName()); // Scenario 1 // 2 shapes - "Text Box" and "Rect" // View1 - "Text Box" enters text edit mode, View 2 - moves the "Rect" around { sd::UndoManager* pUndoManager = pDocument->GetUndoManager(); CPPUNIT_ASSERT_EQUAL(size_t(0), pUndoManager->GetUndoActionCount()); pView1->SdrBeginTextEdit(pTextBoxObject); CPPUNIT_ASSERT_EQUAL(true, pView1->IsTextEdit()); CPPUNIT_ASSERT_EQUAL(false, pView2->IsTextEdit()); // Local undo count for View1 is 0 CPPUNIT_ASSERT_EQUAL(size_t(0), pView1->getViewLocalUndoManager()->GetUndoActionCount()); // Write 'test' in View1 SfxStringItem aInputString(SID_ATTR_CHAR, u"test"_ustr); pViewShell1->GetViewFrame()->GetDispatcher()->ExecuteList(SID_ATTR_CHAR, SfxCallMode::SYNCHRON, { &aInputString }); // Local undo count for View1 is now 1 CPPUNIT_ASSERT_EQUAL(size_t(1), pView1->getViewLocalUndoManager()->GetUndoActionCount()); // Mark rectangle object pView2->MarkObj(pRectangleObject, pView2->GetSdrPageView()); // Check the initial position of the object tools::Rectangle aRectangle = pRectangleObject->GetLogicRect(); CPPUNIT_ASSERT_EQUAL(6250L, aRectangle.TopLeft().X()); CPPUNIT_ASSERT_EQUAL(7000L, aRectangle.TopLeft().Y()); CPPUNIT_ASSERT_EQUAL(6501L, aRectangle.GetWidth()); CPPUNIT_ASSERT_EQUAL(4501L, aRectangle.GetHeight()); // On View2 - Move handle 0 on the shape to a new position - resize Point aNewPosition = aRectangle.TopLeft() + Point(-1250, -1000); pView2->MoveShapeHandle(0, aNewPosition, -1); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(size_t(1), pUndoManager->GetUndoActionCount()); // Check the object has a new size aRectangle = pRectangleObject->GetLogicRect(); CPPUNIT_ASSERT_EQUAL(5000L, aRectangle.TopLeft().X()); CPPUNIT_ASSERT_EQUAL(6000L, aRectangle.TopLeft().Y()); CPPUNIT_ASSERT_EQUAL(7751L, aRectangle.GetWidth()); CPPUNIT_ASSERT_EQUAL(5501L, aRectangle.GetHeight()); // View1 is still in text edit mode... CPPUNIT_ASSERT_EQUAL(true, pView1->IsTextEdit()); CPPUNIT_ASSERT_EQUAL(false, pView2->IsTextEdit()); // On View2 - relative move the shape to a different position pView2->MoveMarkedObj(Size(1000, 2000), /*bCopy=*/false); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(size_t(2), pUndoManager->GetUndoActionCount()); // Check the object is at a different position aRectangle = pRectangleObject->GetLogicRect(); CPPUNIT_ASSERT_EQUAL(6000L, aRectangle.TopLeft().X()); CPPUNIT_ASSERT_EQUAL(8000L, aRectangle.TopLeft().Y()); CPPUNIT_ASSERT_EQUAL(7751L, aRectangle.GetWidth()); CPPUNIT_ASSERT_EQUAL(5501L, aRectangle.GetHeight()); // View1 is still in text edit mode... CPPUNIT_ASSERT_EQUAL(true, pView1->IsTextEdit()); CPPUNIT_ASSERT_EQUAL(false, pView2->IsTextEdit()); // End Text edit - check undo count increase from 2 -> 3 CPPUNIT_ASSERT_EQUAL(size_t(2), pUndoManager->GetUndoActionCount()); pView1->SdrEndTextEdit(); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(size_t(3), pUndoManager->GetUndoActionCount()); // Check that both views exited the text edit mode CPPUNIT_ASSERT_EQUAL(false, pView1->IsTextEdit()); CPPUNIT_ASSERT_EQUAL(false, pView2->IsTextEdit()); } // Scenario 2 // 1 shapes - "Text Box" // View1 - "Text Box" enters text edit mode, View 2 - moves the "Text Box" around { sd::UndoManager* pUndoManager = pDocument->GetUndoManager(); CPPUNIT_ASSERT_EQUAL(size_t(3), pUndoManager->GetUndoActionCount()); pView1->SdrBeginTextEdit(pTextBoxObject); CPPUNIT_ASSERT_EQUAL(true, pView1->IsTextEdit()); CPPUNIT_ASSERT_EQUAL(false, pView2->IsTextEdit()); // Local undo count for View1 is 0 CPPUNIT_ASSERT_EQUAL(size_t(0), pView1->getViewLocalUndoManager()->GetUndoActionCount()); // Write 'test' in View1 SfxStringItem aInputString(SID_ATTR_CHAR, u"test"_ustr); pViewShell1->GetViewFrame()->GetDispatcher()->ExecuteList(SID_ATTR_CHAR, SfxCallMode::SYNCHRON, { &aInputString }); // Local undo count for View1 is now 1 CPPUNIT_ASSERT_EQUAL(size_t(1), pView1->getViewLocalUndoManager()->GetUndoActionCount()); // Mark rectangle object pView2->MarkObj(pTextBoxObject, pView2->GetSdrPageView()); // Check the initial position of the object tools::Rectangle aRectangle = pTextBoxObject->GetLogicRect(); CPPUNIT_ASSERT_EQUAL(2250L, aRectangle.TopLeft().X()); CPPUNIT_ASSERT_EQUAL(2000L, aRectangle.TopLeft().Y()); CPPUNIT_ASSERT_EQUAL(4501L, aRectangle.GetWidth()); CPPUNIT_ASSERT_EQUAL(2001L, aRectangle.GetHeight()); // On View2 - Move handle 0 on the shape to a new position - resize Point aNewPosition = aRectangle.TopLeft() + Point(-1250, -1000); pView2->MoveShapeHandle(0, aNewPosition, -1); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(size_t(4), pUndoManager->GetUndoActionCount()); // Check the object has a new size aRectangle = pTextBoxObject->GetLogicRect(); CPPUNIT_ASSERT_EQUAL(1000L, aRectangle.TopLeft().X()); CPPUNIT_ASSERT_EQUAL(1000L, aRectangle.TopLeft().Y()); CPPUNIT_ASSERT_EQUAL(4990L, aRectangle.GetWidth()); CPPUNIT_ASSERT_EQUAL(2175L, aRectangle.GetHeight()); // View1 is still in text edit mode... CPPUNIT_ASSERT_EQUAL(true, pView1->IsTextEdit()); CPPUNIT_ASSERT_EQUAL(false, pView2->IsTextEdit()); // On View2 - relative move the shape to a different position pView2->MoveMarkedObj(Size(1000, 2000), /*bCopy=*/false); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(size_t(5), pUndoManager->GetUndoActionCount()); // Check the object is at a different position aRectangle = pTextBoxObject->GetLogicRect(); CPPUNIT_ASSERT_EQUAL(2000L, aRectangle.TopLeft().X()); CPPUNIT_ASSERT_EQUAL(3000L, aRectangle.TopLeft().Y()); CPPUNIT_ASSERT_EQUAL(4990L, aRectangle.GetWidth()); CPPUNIT_ASSERT_EQUAL(2175L, aRectangle.GetHeight()); // View1 is still in text edit mode... CPPUNIT_ASSERT_EQUAL(true, pView1->IsTextEdit()); CPPUNIT_ASSERT_EQUAL(false, pView2->IsTextEdit()); // End Text edit - check undo count increase from 5 -> 6 CPPUNIT_ASSERT_EQUAL(size_t(5), pUndoManager->GetUndoActionCount()); pView1->SdrEndTextEdit(); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(size_t(6), pUndoManager->GetUndoActionCount()); // Check that both views exited the text edit mode CPPUNIT_ASSERT_EQUAL(false, pView1->IsTextEdit()); CPPUNIT_ASSERT_EQUAL(false, pView2->IsTextEdit()); } // Scenario 3 // 1 shapes - "Table1" // View1 - "Table1" enters text edit mode, View 2 - moves the "Table1" around { sd::UndoManager* pUndoManager = pDocument->GetUndoManager(); CPPUNIT_ASSERT_EQUAL(size_t(6), pUndoManager->GetUndoActionCount()); pView1->SdrBeginTextEdit(pTableObject); CPPUNIT_ASSERT_EQUAL(true, pView1->IsTextEdit()); CPPUNIT_ASSERT_EQUAL(false, pView2->IsTextEdit()); // Local undo count for View1 is 0 CPPUNIT_ASSERT_EQUAL(size_t(0), pView1->getViewLocalUndoManager()->GetUndoActionCount()); // Write 'test' in View1 SfxStringItem aInputString(SID_ATTR_CHAR, u"test"_ustr); pViewShell1->GetViewFrame()->GetDispatcher()->ExecuteList(SID_ATTR_CHAR, SfxCallMode::SYNCHRON, { &aInputString }); // Local undo count for View1 is now 1 CPPUNIT_ASSERT_EQUAL(size_t(1), pView1->getViewLocalUndoManager()->GetUndoActionCount()); // Mark rectangle object pView2->MarkObj(pTableObject, pView2->GetSdrPageView()); // Check the initial position of the table tools::Rectangle aRectangle = pTableObject->GetLogicRect(); CPPUNIT_ASSERT_EQUAL(2919L, aRectangle.TopLeft().X()); CPPUNIT_ASSERT_EQUAL(18063L, aRectangle.TopLeft().Y()); CPPUNIT_ASSERT_EQUAL(14099L, aRectangle.GetWidth()); CPPUNIT_ASSERT_EQUAL(5999L, aRectangle.GetHeight()); // On View2 - relative move the shape to a different position pView2->MoveMarkedObj(Size(1000, 2000), /*bCopy=*/false); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(size_t(7), pUndoManager->GetUndoActionCount()); // Check the object is at a different position aRectangle = pTableObject->GetLogicRect(); CPPUNIT_ASSERT_EQUAL(3919L, aRectangle.TopLeft().X()); CPPUNIT_ASSERT_EQUAL(20063L, aRectangle.TopLeft().Y()); CPPUNIT_ASSERT_EQUAL(14099L, aRectangle.GetWidth()); CPPUNIT_ASSERT_EQUAL(5999L, aRectangle.GetHeight()); // View1 is still in text edit mode... CPPUNIT_ASSERT_EQUAL(true, pView1->IsTextEdit()); CPPUNIT_ASSERT_EQUAL(false, pView2->IsTextEdit()); // End Text edit - check undo count increase from 7 -> 8 CPPUNIT_ASSERT_EQUAL(size_t(7), pUndoManager->GetUndoActionCount()); pView1->SdrEndTextEdit(); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(size_t(8), pUndoManager->GetUndoActionCount()); // Check that both views exited the text edit mode CPPUNIT_ASSERT_EQUAL(false, pView1->IsTextEdit()); CPPUNIT_ASSERT_EQUAL(false, pView2->IsTextEdit()); } } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSidebarHide) { // Given an impress document, with a visible sidebar: createDoc("dummy.odp"); ViewCallback aView; sfx2::sidebar::Sidebar::Setup(u""); Scheduler::ProcessEventsToIdle(); aView.m_aStateChanges.clear(); // When hiding the slide layout deck: dispatchCommand(mxComponent, u".uno:ModifyPage"_ustr, {}); // Then make sure we get a state change for this, in JSON format: auto it = aView.m_aStateChanges.find(".uno:ModifyPage"); // Without the accompanying fix in place, this test would have failed, we got the state change // in plain text, which was inconsistent (show was JSON, hide was plain text). CPPUNIT_ASSERT(it != aView.m_aStateChanges.end()); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testStartPresentation) { SdXImpressDocument* pXImpressDocument = createDoc("test.ppsx"); ViewCallback aView; CPPUNIT_ASSERT(pXImpressDocument->GetDoc()->GetStartWithPresentation()); Scheduler::ProcessEventsToIdle(); const auto it = aView.m_aStateChanges.find(".uno:StartWithPresentation"); CPPUNIT_ASSERT(it != aView.m_aStateChanges.end()); const auto value = it->second; CPPUNIT_ASSERT(value.get_child_optional("state").has_value()); CPPUNIT_ASSERT_EQUAL(std::string("true"), value.get_child("state").get_value()); } namespace { boost::property_tree::ptree const& child_at(boost::property_tree::ptree const& rTree, std::string_view aName, size_t nIndex) { return std::next(rTree.get_child(std::string(aName)).find(""), nIndex)->second; } bool has_child(boost::property_tree::ptree const& rTree, std::string_view aName) { return rTree.count(std::string(aName)) > 0; } } // end anonymous ns CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testPresentationInfo) { SdXImpressDocument* pXImpressDocument = createDoc("PresentationInfoTest.odp"); pXImpressDocument->initializeForTiledRendering(uno::Sequence()); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); CPPUNIT_ASSERT(pViewShell); Scheduler::ProcessEventsToIdle(); SdPage* pPage = pViewShell->GetActualPage(); CPPUNIT_ASSERT(pPage); OString aString = pXImpressDocument->getPresentationInfo(); boost::property_tree::ptree aTree; std::stringstream aStream((std::string(aString))); boost::property_tree::read_json(aStream, aTree); CPPUNIT_ASSERT_EQUAL(15875, aTree.get_child("docWidth").get_value()); CPPUNIT_ASSERT_EQUAL(8930, aTree.get_child("docHeight").get_value()); CPPUNIT_ASSERT_EQUAL(size_t(5), aTree.get_child("slides").size()); // Slide Index 0 { const boost::property_tree::ptree& rChild = child_at(aTree, "slides", 0); CPPUNIT_ASSERT_EQUAL(0, rChild.get_child("index").get_value()); CPPUNIT_ASSERT_EQUAL(false, rChild.get_child("empty").get_value()); // Check only that these exist CPPUNIT_ASSERT(has_child(rChild, "hash")); CPPUNIT_ASSERT(has_child(rChild, "masterPage")); CPPUNIT_ASSERT(has_child(rChild, "masterPageObjectsVisibility")); // Transitions CPPUNIT_ASSERT_EQUAL(std::string("BarWipe"), rChild.get_child("transitionType").get_value()); CPPUNIT_ASSERT_EQUAL(std::string("TopToBottom"), rChild.get_child("transitionSubtype").get_value()); CPPUNIT_ASSERT_EQUAL(false, rChild.get_child("transitionDirection").get_value()); } // Slide Index 1 { const boost::property_tree::ptree& rChild = child_at(aTree, "slides", 1); CPPUNIT_ASSERT_EQUAL(1, rChild.get_child("index").get_value()); CPPUNIT_ASSERT_EQUAL(false, rChild.get_child("empty").get_value()); // Check only that these exist CPPUNIT_ASSERT(has_child(rChild, "hash")); CPPUNIT_ASSERT(has_child(rChild, "masterPage")); CPPUNIT_ASSERT(has_child(rChild, "masterPageObjectsVisibility")); // Transitions CPPUNIT_ASSERT_EQUAL(std::string("FanWipe"), rChild.get_child("transitionType").get_value()); CPPUNIT_ASSERT_EQUAL(std::string("CenterTop"), rChild.get_child("transitionSubtype").get_value()); CPPUNIT_ASSERT_EQUAL(true, rChild.get_child("transitionDirection").get_value()); } // Slide Index 2 { const boost::property_tree::ptree& rChild = child_at(aTree, "slides", 2); CPPUNIT_ASSERT_EQUAL(2, rChild.get_child("index").get_value()); CPPUNIT_ASSERT_EQUAL(false, rChild.get_child("empty").get_value()); // Check only that these exist CPPUNIT_ASSERT(has_child(rChild, "hash")); CPPUNIT_ASSERT(has_child(rChild, "masterPage")); CPPUNIT_ASSERT(has_child(rChild, "masterPageObjectsVisibility")); // Transitions CPPUNIT_ASSERT_EQUAL(std::string("BlindWipe"), rChild.get_child("transitionType").get_value()); CPPUNIT_ASSERT_EQUAL(std::string("Vertical"), rChild.get_child("transitionSubtype").get_value()); CPPUNIT_ASSERT_EQUAL(true, rChild.get_child("transitionDirection").get_value()); } // Slide Index 3 - Hidden { const boost::property_tree::ptree& rChild = child_at(aTree, "slides", 3); CPPUNIT_ASSERT_EQUAL(3, rChild.get_child("index").get_value()); CPPUNIT_ASSERT_EQUAL(true, rChild.get_child("hidden").get_value()); } // Slide Index 4 { const boost::property_tree::ptree& rChild = child_at(aTree, "slides", 4); CPPUNIT_ASSERT_EQUAL(4, rChild.get_child("index").get_value()); CPPUNIT_ASSERT_EQUAL(false, rChild.get_child("empty").get_value()); // Check only that these exist CPPUNIT_ASSERT(has_child(rChild, "hash")); CPPUNIT_ASSERT(has_child(rChild, "masterPage")); CPPUNIT_ASSERT(has_child(rChild, "masterPageObjectsVisibility")); // Transitions CPPUNIT_ASSERT_EQUAL(std::string("Fade"), rChild.get_child("transitionType").get_value()); CPPUNIT_ASSERT_EQUAL(std::string("FadeOverColor"), rChild.get_child("transitionSubtype").get_value()); CPPUNIT_ASSERT_EQUAL(true, rChild.get_child("transitionDirection").get_value()); } } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSlideshowLayeredRendering) { // Check rendering of slideshow layers (as in the document): // - master slide layer // - main slide layer SdXImpressDocument* pXImpressDocument = createDoc("SlideRenderingTest.odp"); pXImpressDocument->initializeForTiledRendering(uno::Sequence()); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); CPPUNIT_ASSERT(pViewShell); SdPage* pPage = pViewShell->GetActualPage(); CPPUNIT_ASSERT(pPage); sal_Int32 nViewWidth = 2000; sal_Int32 nViewHeight = 2000; CPPUNIT_ASSERT(pXImpressDocument->createSlideRenderer(0, nViewWidth, nViewHeight, true, true)); CPPUNIT_ASSERT_EQUAL(2000, nViewWidth); CPPUNIT_ASSERT_EQUAL(1125, nViewHeight); const Color aTransparentColor(ColorAlpha, 0x00000000); // Background Layer - TODO { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); // TODO - check JSON content BitmapEx aBitmapEx = vcl::bitmap::CreateFromData(pBuffer.data(), nViewWidth, nViewHeight, nViewWidth * 4, /*nBitsPerPixel*/32, true, true); debugWriteImageToFile(1, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); // top-left corner CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(20, 20)); // bottom-left corner CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(20, nViewHeight - 20)); // bottom-right corner CPPUNIT_ASSERT_EQUAL(Color(0xff, 0xd0, 0x40), aBitmapEx.GetPixelColor(nViewWidth - 20, nViewHeight - 20)); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); // TODO - check JSON content debugWriteImageToFile(2, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); BitmapEx aBitmapEx = vcl::bitmap::CreateFromData(pBuffer.data(), nViewWidth, nViewHeight, nViewWidth * 4, /*nBitsPerPixel*/32, true, true); // top-left corner CPPUNIT_ASSERT_EQUAL(Color(0x00, 0x50, 0x90), aBitmapEx.GetPixelColor(20, 20)); // bottom-left corner CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(20, nViewHeight - 20)); // bottom-right corner CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(nViewWidth - 20, nViewHeight - 20)); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); } pXImpressDocument->postSlideshowCleanup(); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSlideshowLayeredRendering_WithFields) { // Check rendering of slideshow layers (as in the document): // - master slide layer // - main slide layer SdXImpressDocument* pXImpressDocument = createDoc("SlideRenderingTest_WithFields.odp"); pXImpressDocument->initializeForTiledRendering(uno::Sequence()); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); CPPUNIT_ASSERT(pViewShell); SdPage* pPage = pViewShell->GetActualPage(); CPPUNIT_ASSERT(pPage); sal_Int32 nViewWidth = 2000; sal_Int32 nViewHeight = 2000; CPPUNIT_ASSERT(pXImpressDocument->createSlideRenderer(0, nViewWidth, nViewHeight, true, true)); CPPUNIT_ASSERT_EQUAL(2000, nViewWidth); CPPUNIT_ASSERT_EQUAL(1125, nViewHeight); const Color aTransparentColor(ColorAlpha, 0x00000000); // Background Layer - TODO { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); // TODO - check JSON content debugWriteImageToFile(1, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); BitmapEx aBitmapEx = vcl::bitmap::CreateFromData(pBuffer.data(), nViewWidth, nViewHeight, nViewWidth * 4, /*nBitsPerPixel*/32, true, true); // top-left corner CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(20, 20)); // bottom-left corner CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(20, nViewHeight - 20)); // bottom-right corner CPPUNIT_ASSERT_EQUAL(Color(0xff, 0xd0, 0x40), aBitmapEx.GetPixelColor(nViewWidth - 20, nViewHeight - 20)); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); // TODO - check JSON content debugWriteImageToFile(2, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); BitmapEx aBitmapEx = vcl::bitmap::CreateFromData(pBuffer.data(), nViewWidth, nViewHeight, nViewWidth * 4, /*nBitsPerPixel*/32, true, true); // top-left corner CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(20, 20)); // bottom-left corner CPPUNIT_ASSERT_EQUAL(Color(0x90, 0x80, 0xff), aBitmapEx.GetPixelColor(20, nViewHeight - 20)); // bottom-right corner CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(nViewWidth - 20, nViewHeight - 20)); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); // TODO - check JSON content debugWriteImageToFile(3, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); BitmapEx aBitmapEx = vcl::bitmap::CreateFromData(pBuffer.data(), nViewWidth, nViewHeight, nViewWidth * 4, /*nBitsPerPixel*/32, true, true); // top-left corner CPPUNIT_ASSERT_EQUAL(Color(0x00, 0x50, 0x90), aBitmapEx.GetPixelColor(20, 20)); // bottom-left corner CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(20, nViewHeight - 20)); // bottom-right corner CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(nViewWidth - 20, nViewHeight - 20)); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); } pXImpressDocument->postSlideshowCleanup(); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSlideshowLayeredRendering_Animations) { SdXImpressDocument* pXImpressDocument = createDoc("SlideRenderingTest_Animations.odp"); pXImpressDocument->initializeForTiledRendering(uno::Sequence()); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); CPPUNIT_ASSERT(pViewShell); SdPage* pPage = pViewShell->GetActualPage(); CPPUNIT_ASSERT(pPage); sal_Int32 nViewWidth = 2000; sal_Int32 nViewHeight = 2000; CPPUNIT_ASSERT(pXImpressDocument->createSlideRenderer(0, nViewWidth, nViewHeight, true, true)); CPPUNIT_ASSERT_EQUAL(2000, nViewWidth); CPPUNIT_ASSERT_EQUAL(1125, nViewHeight); { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"group\": \"Background\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"index\": 0") >= 0); CPPUNIT_ASSERT_EQUAL(-1, rJsonMsg.indexOf(u"\"hash\"")); CPPUNIT_ASSERT_EQUAL(-1, rJsonMsg.indexOf(u"\"initVisible\"")); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"bitmap\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"content\": { \"type\": \"%IMAGETYPE%\", \"checksum\": \"%IMAGECHECKSUM%\"}") >= 0); debugWriteImageToFile(0, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"group\": \"MasterPage\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"index\": 0") >= 0); CPPUNIT_ASSERT_EQUAL(-1, rJsonMsg.indexOf(u"\"hash\"")); CPPUNIT_ASSERT_EQUAL(-1, rJsonMsg.indexOf(u"\"initVisible\"")); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"bitmap\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"content\": { \"type\": \"%IMAGETYPE%\", \"checksum\": \"%IMAGECHECKSUM%\"}") >= 0); debugWriteImageToFile(1, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"group\": \"MasterPage\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"index\": 1") >= 0); CPPUNIT_ASSERT_EQUAL(-1, rJsonMsg.indexOf(u"\"hash\"")); CPPUNIT_ASSERT_EQUAL(-1, rJsonMsg.indexOf(u"\"initVisible\"")); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"bitmap\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"content\": { \"type\": \"%IMAGETYPE%\", \"checksum\": \"%IMAGECHECKSUM%\"}") >= 0); debugWriteImageToFile(2, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"group\": \"DrawPage\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"index\": 0") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"hash\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"animated\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"initVisible\": true") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"content\": { \"type\": \"%IMAGETYPE%\", \"checksum\": \"%IMAGECHECKSUM%\"}") >= 0); debugWriteImageToFile(3, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"group\": \"DrawPage\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"index\": 1") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"hash\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"initVisible\": false") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"animated\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"content\": { \"type\": \"%IMAGETYPE%\", \"checksum\": \"%IMAGECHECKSUM%\"}") >= 0); debugWriteImageToFile(4, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); } pXImpressDocument->postSlideshowCleanup(); } CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSlideshowLayeredRendering_Animations2) { SdXImpressDocument* pXImpressDocument = createDoc("anim.odp"); pXImpressDocument->initializeForTiledRendering(uno::Sequence()); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); CPPUNIT_ASSERT(pViewShell); SdPage* pPage = pViewShell->GetActualPage(); CPPUNIT_ASSERT(pPage); sal_Int32 nViewWidth = 2000; sal_Int32 nViewHeight = 2000; CPPUNIT_ASSERT(pXImpressDocument->createSlideRenderer(0, nViewWidth, nViewHeight, true, true)); CPPUNIT_ASSERT_EQUAL(2000, nViewWidth); CPPUNIT_ASSERT_EQUAL(1125, nViewHeight); { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"group\": \"Background\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"index\": 0") >= 0); CPPUNIT_ASSERT_EQUAL(-1, rJsonMsg.indexOf(u"\"hash\"")); CPPUNIT_ASSERT_EQUAL(-1, rJsonMsg.indexOf(u"\"initVisible\"")); CPPUNIT_ASSERT_EQUAL(-1, rJsonMsg.indexOf(u"\"bounds\"")); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"bitmap\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"content\": { \"type\": \"%IMAGETYPE%\", \"checksum\": \"%IMAGECHECKSUM%\"}") >= 0); debugWriteImageToFile(0, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"group\": \"MasterPage\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"index\": 0") >= 0); CPPUNIT_ASSERT_EQUAL(-1, rJsonMsg.indexOf(u"\"bounds\"")); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"bitmap\"") >= 0); debugWriteImageToFile(1, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"group\": \"MasterPage\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"index\": 1") >= 0); CPPUNIT_ASSERT_EQUAL(-1, rJsonMsg.indexOf(u"\"bounds\"")); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"bitmap\"") >= 0); debugWriteImageToFile(2, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"group\": \"DrawPage\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"index\": 0") >= 0); CPPUNIT_ASSERT_EQUAL(-1, rJsonMsg.indexOf(u"\"initVisible\"")); CPPUNIT_ASSERT_EQUAL(-1, rJsonMsg.indexOf(u"\"bounds\"")); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"bitmap\"") >= 0); debugWriteImageToFile(3, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"group\": \"DrawPage\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"index\": 1") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"initVisible\": false") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"animated\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"bitmap\"") >= 0); debugWriteImageToFile(4, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"group\": \"DrawPage\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"index\": 2") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"initVisible\": true") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"bounds\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"animated\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"bitmap\"") >= 0); debugWriteImageToFile(5, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"group\": \"DrawPage\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"index\": 3") >= 0); CPPUNIT_ASSERT_EQUAL(-1, rJsonMsg.indexOf(u"\"initVisible\"")); CPPUNIT_ASSERT_EQUAL(-1, rJsonMsg.indexOf(u"\"bounds\"")); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"bitmap\"") >= 0); debugWriteImageToFile(6, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); CPPUNIT_ASSERT(bIsBitmapLayer); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"group\": \"DrawPage\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"index\": 4") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"initVisible\": false") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"bounds\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"animated\"") >= 0); CPPUNIT_ASSERT(rJsonMsg.indexOf(u"\"type\": \"bitmap\"") >= 0); debugWriteImageToFile(7, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); } { std::vector pBuffer(nViewWidth * nViewHeight * 4); bool bIsBitmapLayer = false; OUString rJsonMsg; CPPUNIT_ASSERT(pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); } pXImpressDocument->postSlideshowCleanup(); } CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */