diff options
Diffstat (limited to 'sw/qa/extras/uiwriter/uiwriter6.cxx')
-rw-r--r-- | sw/qa/extras/uiwriter/uiwriter6.cxx | 3471 |
1 files changed, 3471 insertions, 0 deletions
diff --git a/sw/qa/extras/uiwriter/uiwriter6.cxx b/sw/qa/extras/uiwriter/uiwriter6.cxx new file mode 100644 index 000000000000..2c0069a7e65a --- /dev/null +++ b/sw/qa/extras/uiwriter/uiwriter6.cxx @@ -0,0 +1,3471 @@ +/* -*- 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 <com/sun/star/drawing/FillStyle.hpp> +#include <swmodeltestbase.hxx> +#include <cntfrm.hxx> +#include <itabenum.hxx> +#include <ndtxt.hxx> +#include <wrtsh.hxx> +#include <edtwin.hxx> +#include <drawdoc.hxx> +#include <view.hxx> +#include <com/sun/star/text/XTextColumns.hpp> + +#include <svx/svdpage.hxx> +#include <svx/svdview.hxx> +#include <svl/itemiter.hxx> +#include <vcl/filter/PDFiumLibrary.hxx> + +#include <dbfld.hxx> +#include <txatbase.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <UndoManager.hxx> + +#include <svl/stritem.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/dispatch.hxx> +#include <cmdid.h> +#include <tools/json_writer.hxx> +#include <tools/UnitConversion.hxx> +#include <boost/property_tree/json_parser.hpp> + +#include <com/sun/star/text/XTextTable.hpp> +#include <com/sun/star/text/XTextViewCursorSupplier.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> +#include <o3tl/cppunittraitshelper.hxx> +#include <swdtflvr.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/scopeguard.hxx> +#include <editeng/swafopt.hxx> +#include <editeng/unolingu.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <vcl/scheduler.hxx> +#include <config_fonts.h> +#include <test/htmltesttools.hxx> +#include <wrthtml.hxx> +#include <dbmgr.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <sortedobjs.hxx> +#include <flyfrms.hxx> +#include <tabfrm.hxx> +#include <unotxdoc.hxx> +#include <wrong.hxx> +#include <com/sun/star/linguistic2/LinguServiceManager.hpp> +#include <com/sun/star/linguistic2/XLinguProperties.hpp> +#include <com/sun/star/linguistic2/XSpellChecker1.hpp> +#include <linguistic/misc.hxx> + +#include <workctrl.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::beans; +using namespace com::sun::star::lang; +using namespace com::sun::star::uno; +using namespace com::sun::star::linguistic2; +using namespace linguistic; + +namespace +{ +sal_Int32 lcl_getAttributeIDFromHints(const SwpHints& hints) +{ + for (size_t i = 0; i < hints.Count(); ++i) + { + const SwTextAttr* hint = hints.Get(i); + if (hint->Which() == RES_TXTATR_AUTOFMT) + { + const SwFormatAutoFormat& rFmt = hint->GetAutoFormat(); + SfxItemIter aIter(*rFmt.GetStyleHandle()); + return aIter.GetCurItem()->Which(); + } + } + return -1; +} + +uno::Reference<XLinguServiceManager2> GetLngSvcMgr_Impl() +{ + uno::Reference<XComponentContext> xContext(comphelper::getProcessComponentContext()); + uno::Reference<XLinguServiceManager2> xRes = LinguServiceManager::create(xContext); + return xRes; +} +} //namespace + +class SwUiWriterTest6 : public SwModelTestBase, public HtmlTestTools +{ +public: + SwUiWriterTest6() + : SwModelTestBase(u"/sw/qa/extras/uiwriter/data/"_ustr) + { + } +}; + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf116640) +{ + createSwDoc(); + + uno::Sequence<beans::PropertyValue> aArgs( + comphelper::InitPropertySequence({ { "Columns", uno::Any(sal_Int32(2)) } })); + + dispatchCommand(mxComponent, u".uno:InsertSection"_ustr, aArgs); + + uno::Reference<text::XTextSectionsSupplier> xTextSectionsSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XIndexAccess> xSections(xTextSectionsSupplier->getTextSections(), + uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xTextSection(xSections->getByIndex(0), uno::UNO_QUERY); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSections->getCount()); + + uno::Reference<text::XTextColumns> xTextColumns + = getProperty<uno::Reference<text::XTextColumns>>(xTextSection, u"TextColumns"_ustr); + CPPUNIT_ASSERT_EQUAL(sal_Int16(2), xTextColumns->getColumnCount()); + + dispatchCommand(mxComponent, u".uno:Undo"_ustr, {}); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xSections->getCount()); + + dispatchCommand(mxComponent, u".uno:Redo"_ustr, {}); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSections->getCount()); + + dispatchCommand(mxComponent, u".uno:Undo"_ustr, {}); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xSections->getCount()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf108524) +{ + createSwDoc("tdf108524.odt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + // In total we expect two cells containing a section. + assertXPath(pXmlDoc, "/root/page/body/tab/row/cell/section", 2); + + assertXPath(pXmlDoc, "/root/page[1]/body/tab/row/cell/section", 1); + // This was 0, section wasn't split, instead it was only on the first page + // and it was cut off. + assertXPath(pXmlDoc, "/root/page[2]/body/tab/row/cell/section", 1); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testLinesInSectionInTable) +{ + // This is similar to testTdf108524(), but the page boundary now is not in + // the middle of a multi-line paragraph: the section only contains oneliner + // paragraphs instead. + createSwDoc("lines-in-section-in-table.odt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + // In total we expect two cells containing a section. + assertXPath(pXmlDoc, "/root/page/body/tab/row/cell/section", 2); + + assertXPath(pXmlDoc, "/root/page[1]/body/tab/row/cell/section", 1); + // This was 0, section wasn't split, instead it was only on the first page + // and it was cut off. + assertXPath(pXmlDoc, "/root/page[2]/body/tab/row/cell/section", 1); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testLinesMoveBackwardsInSectionInTable) +{ +#if HAVE_MORE_FONTS + // Assert that paragraph "4" is on page 1 and "5" is on page 2. + createSwDoc("lines-in-section-in-table.odt"); + SwDoc* pDoc = getSwDoc(); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "/root/page", 2); + SwNodeOffset nPara4Node( + getXPath(pXmlDoc, "/root/page[1]/body/tab/row/cell[1]/section/txt[last()]", "txtNodeIndex") + .toUInt32()); + CPPUNIT_ASSERT_EQUAL(u"4"_ustr, pDoc->GetNodes()[nPara4Node]->GetTextNode()->GetText()); + SwNodeOffset nPara5Node( + getXPath(pXmlDoc, "/root/page[2]/body/tab/row/cell[1]/section/txt[1]", "txtNodeIndex") + .toUInt32()); + CPPUNIT_ASSERT_EQUAL(u"5"_ustr, pDoc->GetNodes()[nPara5Node]->GetTextNode()->GetText()); + + // Remove paragraph "4". + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + while (pWrtShell->GetCursor()->GetPointNode().GetIndex() < nPara4Node) + pWrtShell->Down(/*bSelect=*/false); + pWrtShell->EndPara(); + pWrtShell->Up(/*bSelect=*/true); + pWrtShell->DelLeft(); + + // Assert that paragraph "5" is now moved back to page 1 and is the last paragraph there. + pXmlDoc = parseLayoutDump(); + SwNodeOffset nPage1LastNode( + getXPath(pXmlDoc, "/root/page[1]/body/tab/row/cell[1]/section/txt[last()]", "txtNodeIndex") + .toUInt32()); + // This was "3", paragraph "4" was deleted, but "5" was not moved backwards from page 2. + CPPUNIT_ASSERT_EQUAL(u"5"_ustr, pDoc->GetNodes()[nPage1LastNode]->GetTextNode()->GetText()); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTableInSection) +{ +#if HAVE_MORE_FONTS + // The document has a section, containing a table that spans over 2 pages. + createSwDoc("table-in-sect.odt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + // In total we expect 4 cells. + assertXPath(pXmlDoc, "/root/page/body/section/tab/row/cell", 4); + + // Assert that on both pages the section contains 2 cells. + assertXPath(pXmlDoc, "/root/page[1]/body/section/tab/row/cell", 2); + assertXPath(pXmlDoc, "/root/page[2]/body/section/tab/row/cell", 2); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTableInNestedSection) +{ +#if HAVE_MORE_FONTS + // The document has a nested section, containing a table that spans over 2 pages. + // This crashed the layout. + createSwDoc("rhbz739252-3.odt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + // Make sure the table is inside a section and spans over 2 pages. + assertXPath(pXmlDoc, "//page[1]//section/tab", 1); + assertXPath(pXmlDoc, "//page[2]//section/tab", 1); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf112741) +{ +#if HAVE_MORE_FONTS + createSwDoc("tdf112741.fodt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + // This was 5 pages. + assertXPath(pXmlDoc, "//page", 4); + assertXPath(pXmlDoc, "//page[1]/body/tab/row/cell/tab/row/cell/section", 1); + assertXPath(pXmlDoc, "//page[2]/body/tab/row/cell/tab/row/cell/section", 1); + // This failed, 3rd page contained no sections. + assertXPath(pXmlDoc, "//page[3]/body/tab/row/cell/tab/row/cell/section", 1); + assertXPath(pXmlDoc, "//page[4]/body/tab/row/cell/tab/row/cell/section", 1); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf112860) +{ +#if HAVE_MORE_FONTS + // The document has a split section inside a nested table, and also a table + // in the footer. + // This crashed the layout. + createSwDoc("tdf112860.fodt"); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf113287) +{ +#if HAVE_MORE_FONTS + createSwDoc("tdf113287.fodt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "//page", 2); + sal_uInt32 nCellTop + = getXPath(pXmlDoc, "//page[2]/body/tab/row/cell[1]/infos/bounds", "top").toUInt32(); + sal_uInt32 nSectionTop + = getXPath(pXmlDoc, "//page[2]/body/tab/row/cell[1]/section/infos/bounds", "top") + .toUInt32(); + // Make sure section frame is inside the cell frame. + // Expected greater than 4593, was only 3714. + CPPUNIT_ASSERT_GREATER(nCellTop, nSectionTop); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf113445) +{ +#if HAVE_MORE_FONTS + // Force multiple-page view. + createSwDoc("tdf113445.fodt"); + SwDocShell* pDocShell = getSwDocShell(); + SwView* pView = pDocShell->GetView(); + pView->SetViewLayout(/*nColumns=*/2, /*bBookMode=*/false); + calcLayout(); + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "//page", 2); + sal_uInt32 nPage1Left = getXPath(pXmlDoc, "//page[1]/infos/bounds", "left").toUInt32(); + sal_uInt32 nPage2Left = getXPath(pXmlDoc, "//page[2]/infos/bounds", "left").toUInt32(); + // Make sure that page 2 is on the right hand side of page 1, not below it. + CPPUNIT_ASSERT_GREATER(nPage1Left, nPage2Left); + + // Insert a new paragraph at the start of the document. + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->StartOfSection(); + pWrtShell->SplitNode(); + pXmlDoc = parseLayoutDump(); + + // Make sure that Table2:C5 and Table2:D5 has its section frame inside the cell frame. + sal_uInt32 nCell3Top + = getXPath(pXmlDoc, "//page[2]/body/tab/row/cell/tab/row[4]/cell[3]/infos/bounds", "top") + .toUInt32(); + sal_uInt32 nSection3Top + = getXPath(pXmlDoc, "//page[2]/body/tab/row/cell/tab/row[4]/cell[3]/section/infos/bounds", + "top") + .toUInt32(); + CPPUNIT_ASSERT_GREATER(nCell3Top, nSection3Top); + sal_uInt32 nCell4Top + = getXPath(pXmlDoc, "//page[2]/body/tab/row/cell/tab/row[4]/cell[4]/infos/bounds", "top") + .toUInt32(); + sal_uInt32 nSection4Top + = getXPath(pXmlDoc, "//page[2]/body/tab/row/cell/tab/row[4]/cell[4]/section/infos/bounds", + "top") + .toUInt32(); + CPPUNIT_ASSERT_GREATER(nCell4Top, nSection4Top); + // Also check if the two cells in the same row have the same top position. + // This was 4818, expected only 1672. + CPPUNIT_ASSERT_EQUAL(nCell3Top, nCell4Top); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf113686) +{ +#if HAVE_MORE_FONTS + createSwDoc("tdf113686.fodt"); + SwDoc* pDoc = getSwDoc(); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "/root/page", 2); + SwNodeOffset nPage1LastNode( + getXPath(pXmlDoc, "/root/page[1]/body/tab/row/cell[1]/tab/row/cell[1]/txt[last()]", + "txtNodeIndex") + .toUInt32()); + CPPUNIT_ASSERT_EQUAL(u"Table2:A1-P10"_ustr, + pDoc->GetNodes()[nPage1LastNode]->GetTextNode()->GetText()); + SwNodeOffset nPage2FirstNode( + getXPath(pXmlDoc, "/root/page[2]/body/tab/row/cell[1]/section/txt[1]", "txtNodeIndex") + .toUInt32()); + CPPUNIT_ASSERT_EQUAL(u"Table1:A1"_ustr, + pDoc->GetNodes()[nPage2FirstNode]->GetTextNode()->GetText()); + + // Remove page 2. + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + while (pWrtShell->GetCursor()->Start()->GetNodeIndex() < nPage1LastNode) + pWrtShell->Down(/*bSelect=*/false); + pWrtShell->EndPara(); + for (int i = 0; i < 3; ++i) + pWrtShell->Up(/*bSelect=*/true); + pWrtShell->DelLeft(); + + // Assert that the second page is removed. + pXmlDoc = parseLayoutDump(); + // This was still 2, content from 2nd page was not moved. + assertXPath(pXmlDoc, "/root/page", 1); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTableInSectionInTable) +{ +#if HAVE_MORE_FONTS + // The document has a table, containing a section, containing a nested + // table. + // This crashed the layout. + createSwDoc("i95698.odt"); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testSectionInTableInTable) +{ +#if HAVE_MORE_FONTS + // The document has a nested table, containing a multi-line section at a + // page boundary. + // This crashed the layout later in SwFrame::IsFootnoteAllowed(). + createSwDoc("tdf112109.fodt"); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testSectionInTableInTable2) +{ +#if HAVE_MORE_FONTS + createSwDoc("split-section-in-nested-table.fodt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + sal_uInt32 nSection1 + = getXPath(pXmlDoc, "//page[1]//body/tab/row/cell/tab/row/cell/section", "id").toUInt32(); + sal_uInt32 nSection1Follow + = getXPath(pXmlDoc, "//page[1]//body/tab/row/cell/tab/row/cell/section", "follow") + .toUInt32(); + // This failed, the section wasn't split inside a nested table. + sal_uInt32 nSection2 + = getXPath(pXmlDoc, "//page[2]//body/tab/row/cell/tab/row/cell/section", "id").toUInt32(); + sal_uInt32 nSection2Precede + = getXPath(pXmlDoc, "//page[2]//body/tab/row/cell/tab/row/cell/section", "precede") + .toUInt32(); + + // Make sure that the first's follow and the second's precede is correct. + CPPUNIT_ASSERT_EQUAL(nSection2, nSection1Follow); + CPPUNIT_ASSERT_EQUAL(nSection1, nSection2Precede); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testSectionInTableInTable3) +{ +#if HAVE_MORE_FONTS + createSwDoc("tdf113153.fodt"); + + uno::Reference<text::XTextTablesSupplier> xTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XIndexAccess> xTables(xTablesSupplier->getTextTables(), + uno::UNO_QUERY); + uno::Reference<container::XNamed> xTable(xTables->getByIndex(1), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(u"Table16"_ustr, xTable->getName()); + + uno::Reference<text::XTextTable> xRowSupplier(xTable, uno::UNO_QUERY); + uno::Reference<table::XTableRows> xRows = xRowSupplier->getRows(); + uno::Reference<beans::XPropertySet> xRow(xRows->getByIndex(1), uno::UNO_QUERY); + xRow->setPropertyValue(u"IsSplitAllowed"_ustr, uno::Any(true)); + // This never returned. + calcLayout(); + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + sal_uInt32 nTable1 = getXPath(pXmlDoc, "//page[1]//body/tab", "id").toUInt32(); + sal_uInt32 nTable1Follow = getXPath(pXmlDoc, "//page[1]//body/tab", "follow").toUInt32(); + sal_uInt32 nTable2 = getXPath(pXmlDoc, "//page[2]//body/tab", "id").toUInt32(); + sal_uInt32 nTable2Precede = getXPath(pXmlDoc, "//page[2]//body/tab", "precede").toUInt32(); + sal_uInt32 nTable2Follow = getXPath(pXmlDoc, "//page[2]//body/tab", "follow").toUInt32(); + sal_uInt32 nTable3 = getXPath(pXmlDoc, "//page[3]//body/tab", "id").toUInt32(); + sal_uInt32 nTable3Precede = getXPath(pXmlDoc, "//page[3]//body/tab", "precede").toUInt32(); + + // Make sure the outer table frames are linked together properly. + CPPUNIT_ASSERT_EQUAL(nTable2, nTable1Follow); + CPPUNIT_ASSERT_EQUAL(nTable1, nTable2Precede); + CPPUNIT_ASSERT_EQUAL(nTable3, nTable2Follow); + CPPUNIT_ASSERT_EQUAL(nTable2, nTable3Precede); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testSectionInTableInTable4) +{ +#if HAVE_MORE_FONTS + createSwDoc("tdf113520.fodt"); + SwDoc* pDoc = getSwDoc(); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "/root/page", 3); + SwNodeOffset nPage1LastNode( + getXPath(pXmlDoc, "/root/page[1]/body/tab/row/cell[1]/tab/row/cell[1]/section/txt[last()]", + "txtNodeIndex") + .toUInt32()); + CPPUNIT_ASSERT_EQUAL(u"Section1:P10"_ustr, + pDoc->GetNodes()[nPage1LastNode]->GetTextNode()->GetText()); + SwNodeOffset nPage3FirstNode( + getXPath(pXmlDoc, "/root/page[3]/body/tab/row/cell[1]/tab/row/cell[1]/section/txt[1]", + "txtNodeIndex") + .toUInt32()); + CPPUNIT_ASSERT_EQUAL(u"Section1:P23"_ustr, + pDoc->GetNodes()[nPage3FirstNode]->GetTextNode()->GetText()); + + // Remove page 2. + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + while (pWrtShell->GetCursor()->Start()->GetNodeIndex() < nPage1LastNode) + pWrtShell->Down(/*bSelect=*/false); + pWrtShell->EndPara(); + while (pWrtShell->GetCursor()->End()->GetNodeIndex() < nPage3FirstNode) + pWrtShell->Down(/*bSelect=*/true); + pWrtShell->EndPara(/*bSelect=*/true); + pWrtShell->DelLeft(); + + // Assert that the page is removed. + pXmlDoc = parseLayoutDump(); + // This was 3, page 2 was emptied, but it wasn't removed. + assertXPath(pXmlDoc, "/root/page", 2); + + // Make sure the outer table frames are linked together properly. + sal_uInt32 nTable1 = getXPath(pXmlDoc, "//page[1]//body/tab", "id").toUInt32(); + sal_uInt32 nTable1Follow = getXPath(pXmlDoc, "//page[1]//body/tab", "follow").toUInt32(); + sal_uInt32 nTable2 = getXPath(pXmlDoc, "//page[2]//body/tab", "id").toUInt32(); + sal_uInt32 nTable2Precede = getXPath(pXmlDoc, "//page[2]//body/tab", "precede").toUInt32(); + CPPUNIT_ASSERT_EQUAL(nTable2, nTable1Follow); + CPPUNIT_ASSERT_EQUAL(nTable1, nTable2Precede); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf112160) +{ +#if HAVE_MORE_FONTS + // Assert that the A2 cell is on page 1. + createSwDoc("tdf112160.fodt"); + SwDoc* pDoc = getSwDoc(); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + SwNodeOffset nA2CellNode(getXPath(pXmlDoc, + "/root/page[1]/body/tab/row[2]/cell[1]/section/txt[last()]", + "txtNodeIndex") + .toUInt32()); + CPPUNIT_ASSERT_EQUAL(u"Table1.A2"_ustr, + pDoc->GetNodes()[nA2CellNode]->GetTextNode()->GetText()); + + // Append a new paragraph to the end of the A2 cell. + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + while (pWrtShell->GetCursor()->GetPointNode().GetIndex() < nA2CellNode) + pWrtShell->Down(/*bSelect=*/false); + pWrtShell->EndPara(); + pWrtShell->SplitNode(); + + // Assert that after A2 got extended, D2 stays on page 1. + pXmlDoc = parseLayoutDump(); + sal_uInt32 nD2CellNode + = getXPath(pXmlDoc, "/root/page[1]/body/tab/row[2]/cell[last()]/section/txt[last()]", + "txtNodeIndex") + .toUInt32(); + // This was Table1.C2, Table1.D2 was moved to the next page, unexpected. + CPPUNIT_ASSERT_EQUAL(u"Table1.D2"_ustr, + pDoc->GetNodes()[SwNodeOffset(nD2CellNode)]->GetTextNode()->GetText()); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf114536) +{ + // This crashed in SwTextFormatter::MergeCharacterBorder() due to a + // use after free. + createSwDoc("tdf114536.odt"); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testParagraphOfTextRange) +{ + createSwDoc("paragraph-of-text-range.odt"); + + // Enter the table. + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->Down(/*bSelect=*/false); + CPPUNIT_ASSERT(pWrtShell->IsCursorInTable()); + // Enter the section. + pWrtShell->Down(/*bSelect=*/false); + CPPUNIT_ASSERT(pWrtShell->IsDirectlyInSection()); + + // Assert that we get the right paragraph object. + uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XTextViewCursorSupplier> xController(xModel->getCurrentController(), + uno::UNO_QUERY); + uno::Reference<text::XTextRange> xViewCursor = xController->getViewCursor(); + // This failed as there were no TextParagraph property. + auto xParagraph = getProperty<uno::Reference<text::XTextRange>>(xViewCursor->getStart(), + u"TextParagraph"_ustr); + CPPUNIT_ASSERT_EQUAL(u"In section"_ustr, xParagraph->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf99689TableOfContents) +{ + createSwDoc("tdf99689.odt"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->GotoNextTOXBase(); + const SwTOXBase* pTOXBase = pWrtShell->GetCurTOX(); + pWrtShell->UpdateTableOf(*pTOXBase); + SwCursorShell* pShell(pDoc->GetEditShell()); + CPPUNIT_ASSERT(pShell); + SwTextNode* pTitleNode = pShell->GetCursor()->GetPointNode().GetTextNode(); + SwNodeIndex aIdx(*pTitleNode); + // skip the title + SwNodes::GoNext(&aIdx); + + // skip the first header. No attributes there. + // next node should contain superscript + SwTextNode* pNext = static_cast<SwTextNode*>(SwNodes::GoNext(&aIdx)); + CPPUNIT_ASSERT(pNext->HasHints()); + sal_uInt16 nAttrType = lcl_getAttributeIDFromHints(pNext->GetSwpHints()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(RES_CHRATR_ESCAPEMENT), nAttrType); + + // next node should contain subscript + pNext = static_cast<SwTextNode*>(SwNodes::GoNext(&aIdx)); + CPPUNIT_ASSERT(pNext->HasHints()); + nAttrType = lcl_getAttributeIDFromHints(pNext->GetSwpHints()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(RES_CHRATR_ESCAPEMENT), nAttrType); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf99689TableOfFigures) +{ + createSwDoc("tdf99689_figures.odt"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->GotoNextTOXBase(); + const SwTOXBase* pTOXBase = pWrtShell->GetCurTOX(); + pWrtShell->UpdateTableOf(*pTOXBase); + SwCursorShell* pShell(pDoc->GetEditShell()); + CPPUNIT_ASSERT(pShell); + SwTextNode* pTitleNode = pShell->GetCursor()->GetPointNode().GetTextNode(); + SwNodeIndex aIdx(*pTitleNode); + + // skip the title + // next node should contain subscript + SwTextNode* pNext = static_cast<SwTextNode*>(SwNodes::GoNext(&aIdx)); + CPPUNIT_ASSERT(pNext->HasHints()); + sal_uInt16 nAttrType = lcl_getAttributeIDFromHints(pNext->GetSwpHints()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(RES_CHRATR_ESCAPEMENT), nAttrType); + + // next node should contain superscript + pNext = static_cast<SwTextNode*>(SwNodes::GoNext(&aIdx)); + CPPUNIT_ASSERT(pNext->HasHints()); + nAttrType = lcl_getAttributeIDFromHints(pNext->GetSwpHints()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(RES_CHRATR_ESCAPEMENT), nAttrType); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf99689TableOfTables) +{ + createSwDoc("tdf99689_tables.odt"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->GotoNextTOXBase(); + const SwTOXBase* pTOXBase = pWrtShell->GetCurTOX(); + pWrtShell->UpdateTableOf(*pTOXBase); + SwCursorShell* pShell(pDoc->GetEditShell()); + CPPUNIT_ASSERT(pShell); + SwTextNode* pTitleNode = pShell->GetCursor()->GetPointNode().GetTextNode(); + SwNodeIndex aIdx(*pTitleNode); + + // skip the title + // next node should contain superscript + SwTextNode* pNext = static_cast<SwTextNode*>(SwNodes::GoNext(&aIdx)); + CPPUNIT_ASSERT(pNext->HasHints()); + sal_uInt16 nAttrType = lcl_getAttributeIDFromHints(pNext->GetSwpHints()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(RES_CHRATR_ESCAPEMENT), nAttrType); + + // next node should contain subscript + pNext = static_cast<SwTextNode*>(SwNodes::GoNext(&aIdx)); + CPPUNIT_ASSERT(pNext->HasHints()); + nAttrType = lcl_getAttributeIDFromHints(pNext->GetSwpHints()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(RES_CHRATR_ESCAPEMENT), nAttrType); +} + +// tdf#112448: Fix: take correct line height +// +// When line metrics is not calculated we need to call CalcRealHeight() +// before usage of the Height() and GetRealHeight(). +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf112448) +{ + createSwDoc("tdf112448.odt"); + + // check actual number of line breaks in the paragraph + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout", 2); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf113790) +{ + createSwDoc("tdf113790.docx"); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + // Create the clipboard document. + SwDoc aClipboard; + aClipboard.SetClipBoard(true); + + // Go to fourth line - to "ABCD" bulleted list item + pWrtShell->Down(/*bSelect=*/false, 4); + pWrtShell->SelPara(nullptr); + CPPUNIT_ASSERT_EQUAL(u"ABCD"_ustr, pWrtShell->GetSelText()); + pWrtShell->Copy(aClipboard); + + // Go down to next-to-last (empty) line above "Title3" + pWrtShell->Down(/*bSelect=*/false, 4); + pWrtShell->Paste(aClipboard); + + // Save it as DOCX & load it again + saveAndReload(u"Office Open XML Text"_ustr); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf157937) +{ + createSwDoc("tdf130088.docx"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + + // select paragraph + pWrtShell->SelPara(nullptr); + + // enable redlining + dispatchCommand(mxComponent, u".uno:TrackChanges"_ustr, {}); + CPPUNIT_ASSERT_MESSAGE("redlining should be on", + pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + + // show changes + CPPUNIT_ASSERT_MESSAGE( + "redlines should be visible", + IDocumentRedlineAccess::IsShowChanges(pDoc->getIDocumentRedlineAccess().GetRedlineFlags())); + + // cycle case with change tracking + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + // This resulted freezing + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf157988) +{ + createSwDoc("tdf130088.docx"); + SwDoc* pDoc = getSwDoc(); + + // select the second word + dispatchCommand(mxComponent, u".uno:GoToNextWord"_ustr, {}); + dispatchCommand(mxComponent, u".uno:SelectWord"_ustr, {}); + + // enable redlining + dispatchCommand(mxComponent, u".uno:TrackChanges"_ustr, {}); + CPPUNIT_ASSERT_MESSAGE("redlining should be on", + pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + + // show changes + CPPUNIT_ASSERT_MESSAGE( + "redlines should be visible", + IDocumentRedlineAccess::IsShowChanges(pDoc->getIDocumentRedlineAccess().GetRedlineFlags())); + + // cycle case with change tracking + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("Integer sodalesSodales")); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + // This was false (missing revert of the tracked change) + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("Integer sodales tincidunt")); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("Integer sodales tincidunt")); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("Integer sodalesSodales")); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("Integer sodales tincidunt")); + + // tdf#141198 cycle case without selection: the word under the cursor + + dispatchCommand(mxComponent, u".uno:Escape"_ustr, {}); + + dispatchCommand(mxComponent, u".uno:GoRight"_ustr, {}); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("Integer sodales tincidunt")); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("Integer sodalesSodales")); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("Integer sodales tincidunt")); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf157667) +{ + createSwDoc("tdf130088.docx"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + + // select the first three words + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 25, /*bBasicCall=*/false); + + // enable redlining + dispatchCommand(mxComponent, u".uno:TrackChanges"_ustr, {}); + CPPUNIT_ASSERT_MESSAGE("redlining should be on", + pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + + // show changes + CPPUNIT_ASSERT_MESSAGE( + "redlines should be visible", + IDocumentRedlineAccess::IsShowChanges(pDoc->getIDocumentRedlineAccess().GetRedlineFlags())); + + // cycle case with change tracking + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith( + "Integer sodalesSodales tinciduntTincidunt tristique.")); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + // This was false (missing revert of the tracked change) + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("Integer sodales tincidunt tristique.")); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith( + "Integer sodalesINTEGER SODALES tincidunt tristique.")); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("Integer sodales tincidunt tristique.")); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith( + "Integer sodalesSodales tinciduntTincidunt tristique.")); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("Integer sodales tincidunt tristique.")); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf158039) +{ + createSwDoc("tdf130088.docx"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + + // select the first sentence + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 26, /*bBasicCall=*/false); + + // enable redlining + dispatchCommand(mxComponent, u".uno:TrackChanges"_ustr, {}); + CPPUNIT_ASSERT_MESSAGE("redlining should be on", + pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + + // show changes + CPPUNIT_ASSERT_MESSAGE( + "redlines should be visible", + IDocumentRedlineAccess::IsShowChanges(pDoc->getIDocumentRedlineAccess().GetRedlineFlags())); + + // cycle case with change tracking + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith( + "Integer sodalesSodales tinciduntTincidunt tristique.")); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + // This was false (missing revert of the tracked change) + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("Integer sodales tincidunt tristique.")); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith( + "Integer sodalesINTEGER SODALES tincidunt tristique.")); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith( + "Integer sodalesSodales tinciduntTincidunt tristique.")); + + dispatchCommand(mxComponent, u".uno:ChangeCaseRotateCase"_ustr, {}); + + CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("Integer sodales tincidunt tristique.")); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf108048) +{ + createSwDoc(); + + uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence({ + { "Kind", uno::Any(sal_Int16(3)) }, + { "TemplateName", uno::Any(u"Default Page Style"_ustr) }, + { "PageNumber", uno::Any(sal_uInt16(6)) }, // Even number to avoid auto-inserted blank page + { "PageNumberFilled", uno::Any(true) }, + }); + dispatchCommand(mxComponent, u".uno:InsertBreak"_ustr, aPropertyValues); + CPPUNIT_ASSERT_EQUAL(2, getParagraphs()); + CPPUNIT_ASSERT_EQUAL(2, getPages()); + + // The inserted page must have page number set to 6 + uno::Reference<text::XTextRange> xPara = getParagraph(2); + sal_uInt16 nPageNumber = getProperty<sal_uInt16>(xPara, u"PageNumberOffset"_ustr); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(6), nPageNumber); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf113481) +{ + createSwDoc("tdf113481-IVS.odt"); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + + // One backspace should completely remove the CJK ideograph variation sequence + pWrtShell->EndPara(); + // Before: U+8FBA U+E0102. After: empty + pWrtShell->DelLeft(); + const uno::Reference<text::XTextRange> xPara1 = getParagraph(1); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xPara1->getString().getLength()); + + // Also variation sequence of weak characters that are treated as CJK script + pWrtShell->Down(false); + pWrtShell->EndPara(); + // Before: U+4E2D U+2205 U+FE00. After: U+4E2D U+2205 + pWrtShell->DelLeft(); + const uno::Reference<text::XTextRange> xPara2 = getParagraph(2); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xPara2->getString().getLength()); + CPPUNIT_ASSERT_EQUAL(u'\x4E2D', xPara2->getString()[0]); + + // Also variation sequence of other scripts + pWrtShell->Down(false); + pWrtShell->EndPara(); + // Before: U+1820 U+180B. After: U+1820 + pWrtShell->DelLeft(); + const uno::Reference<text::XTextRange> xPara3 = getParagraph(3); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xPara3->getString().getLength()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf115013) +{ + static constexpr OUString sColumnName(u"Name with spaces, \"quotes\" and \\backslashes"_ustr); + + utl::TempFileNamed aTempDir(nullptr, true); + aTempDir.EnableKillingFile(); + const OUString aWorkDir = aTempDir.GetURL(); + + //create new writer document + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + + { + // Load and register data source + OUString sDataSource + = SwDBManager::LoadAndRegisterDataSource(createFileURL(u"datasource.ods"), &aWorkDir); + CPPUNIT_ASSERT(!sDataSource.isEmpty()); + + // Insert a new field type for the mailmerge field + SwDBData aDBData; + aDBData.sDataSource = sDataSource; + aDBData.sCommand = "Sheet1"; + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwDBFieldType* pFieldType = static_cast<SwDBFieldType*>( + pWrtShell->InsertFieldType(SwDBFieldType(pDoc, sColumnName, aDBData))); + CPPUNIT_ASSERT(pFieldType); + + // Insert the field into document + SwDBField aField(pFieldType); + pWrtShell->InsertField2(aField); + } + // Save it as DOCX & load it again + saveAndReload(u"Office Open XML Text"_ustr); + pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwCursorShell* pShell(pDoc->GetEditShell()); + CPPUNIT_ASSERT(pShell); + SwPaM* pCursor = pShell->GetCursor(); + CPPUNIT_ASSERT(pCursor); + + // Get the field at the beginning of the document + SwDBField* pField = dynamic_cast<SwDBField*>(SwCursorShell::GetFieldAtCursor(pCursor, true)); + CPPUNIT_ASSERT(pField); + OUString sColumn = static_cast<SwDBFieldType*>(pField->GetTyp())->GetColumnName(); + // The column name must come correct after round trip + CPPUNIT_ASSERT_EQUAL(sColumnName, sColumn); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf115065) +{ + // In the document, the tables have table style assigned + // Source table (first one) has two rows; + // destination (second one) has only one row + createSwDoc("tdf115065.odt"); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + pWrtShell->GotoTable(UIName(u"Table2"_ustr)); + SwRect aRect = pWrtShell->GetCurrFrame()->getFrameArea(); + // Destination point is the middle of the first cell of second table + Point ptTo(aRect.Left() + aRect.Width() / 2, aRect.Top() + aRect.Height() / 2); + + pWrtShell->GotoTable(UIName(u"Table1"_ustr)); + aRect = pWrtShell->GetCurrFrame()->getFrameArea(); + // Source point is the middle of the first cell of first table + Point ptFrom(aRect.Left() + aRect.Width() / 2, aRect.Top() + aRect.Height() / 2); + + pWrtShell->SelTableCol(); + // The copy operation (or closing document after that) segfaulted + pWrtShell->Copy(*pWrtShell, ptFrom, ptTo); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf84806_MovingMultipleTableRows) +{ + // Moving of multiple table rows. + // Source table (first one) has two rows; + // destination (second one) has only one row + createSwDoc("tdf115065.odt"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + uno::Reference<text::XTextTablesSupplier> xTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XIndexAccess> xTables(xTablesSupplier->getTextTables(), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTables->getCount()); + uno::Reference<container::XNameAccess> xTableNames = xTablesSupplier->getTextTables(); + CPPUNIT_ASSERT(xTableNames->hasByName(u"Table1"_ustr)); + CPPUNIT_ASSERT(xTableNames->hasByName(u"Table2"_ustr)); + uno::Reference<text::XTextTable> xTable1(xTableNames->getByName(u"Table1"_ustr), + uno::UNO_QUERY); + uno::Reference<text::XTextTable> xTable2(xTableNames->getByName(u"Table2"_ustr), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTable2->getRows()->getCount()); + + // without redlining + CPPUNIT_ASSERT_MESSAGE("redlining should be off", + !pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + + sw::UndoManager& rUndoManager = pDoc->GetUndoManager(); + + pWrtShell->GotoTable(UIName(u"Table2"_ustr)); + SwRect aRect = pWrtShell->GetCurrFrame()->getFrameArea(); + // Destination point is the middle of the first cell of second table + Point ptTo(aRect.Left() + aRect.Width() / 2, aRect.Top() + aRect.Height() / 2); + + // Move rows of the first table into the second table + pWrtShell->GotoTable(UIName(u"Table1"_ustr)); + pWrtShell->SelTable(); + rtl::Reference<SwTransferable> xTransfer = new SwTransferable(*pWrtShell); + xTransfer->PrivateDrop(*pWrtShell, ptTo, /*bMove=*/true, /*bXSelection=*/true); + + // This was 2 tables + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTables->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable2->getRows()->getCount()); + + // Undo results 2 tables + rUndoManager.Undo(); + uno::Reference<container::XIndexAccess> xTables2(xTablesSupplier->getTextTables(), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTables2->getCount()); + uno::Reference<text::XTextTable> xTable1b(xTableNames->getByName(u"Table1"_ustr), + uno::UNO_QUERY); + uno::Reference<text::XTextTable> xTable2b(xTableNames->getByName(u"Table2"_ustr), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable1b->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTable2b->getRows()->getCount()); + + // FIXME assert with Redo() +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf147181_TrackedMovingOfMultipleTableRows) +{ + // Tracked moving of multiple table rows. + // Source table (first one) has two rows; + // destination (second one) has only one row + createSwDoc("tdf115065.odt"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + uno::Reference<text::XTextTablesSupplier> xTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XIndexAccess> xTables(xTablesSupplier->getTextTables(), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTables->getCount()); + uno::Reference<container::XNameAccess> xTableNames = xTablesSupplier->getTextTables(); + CPPUNIT_ASSERT(xTableNames->hasByName(u"Table1"_ustr)); + CPPUNIT_ASSERT(xTableNames->hasByName(u"Table2"_ustr)); + uno::Reference<text::XTextTable> xTable1(xTableNames->getByName(u"Table1"_ustr), + uno::UNO_QUERY); + uno::Reference<text::XTextTable> xTable2(xTableNames->getByName(u"Table2"_ustr), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTable2->getRows()->getCount()); + + // FIXME: doesn't work with empty rows, yet + pWrtShell->Insert(u"x"_ustr); + pWrtShell->Down(false); + pWrtShell->Insert(u"x"_ustr); + + // enable redlining + dispatchCommand(mxComponent, u".uno:TrackChanges"_ustr, {}); + CPPUNIT_ASSERT_MESSAGE("redlining should be on", + pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + + // show changes + CPPUNIT_ASSERT_MESSAGE( + "redlines should be visible", + IDocumentRedlineAccess::IsShowChanges(pDoc->getIDocumentRedlineAccess().GetRedlineFlags())); + + sw::UndoManager& rUndoManager = pDoc->GetUndoManager(); + + pWrtShell->GotoTable(UIName(u"Table2"_ustr)); + SwRect aRect = pWrtShell->GetCurrFrame()->getFrameArea(); + // Destination point is the middle of the first cell of second table + Point ptTo(aRect.Left() + aRect.Width() / 2, aRect.Top() + aRect.Height() / 2); + + // Move rows of the first table into the second table + pWrtShell->GotoTable(UIName(u"Table1"_ustr)); + pWrtShell->SelTable(); + rtl::Reference<SwTransferable> xTransfer = new SwTransferable(*pWrtShell); + xTransfer->PrivateDrop(*pWrtShell, ptTo, /*bMove=*/true, /*bXSelection=*/true); + + // still 2 tables, but the second one has got 3 rows + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTables->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable2->getRows()->getCount()); + + // accept changes results 1 table (removing moved table) + dispatchCommand(mxComponent, u".uno:AcceptAllTrackedChanges"_ustr, {}); + uno::Reference<container::XIndexAccess> xTables2(xTablesSupplier->getTextTables(), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTables2->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable2->getRows()->getCount()); + + // Undo results 2 tables + rUndoManager.Undo(); + rUndoManager.Undo(); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTables2->getCount()); + uno::Reference<text::XTextTable> xTable1b(xTableNames->getByName(u"Table1"_ustr), + uno::UNO_QUERY); + uno::Reference<text::XTextTable> xTable2b(xTableNames->getByName(u"Table2"_ustr), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable1b->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTable2b->getRows()->getCount()); + + // reject changes results 2 table again, with the original row counts + dispatchCommand(mxComponent, u".uno:RejectAllTrackedChanges"_ustr, {}); + uno::Reference<container::XIndexAccess> xTables3(xTablesSupplier->getTextTables(), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTables3->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable1b->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTable2b->getRows()->getCount()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf157492_TrackedMovingRow) +{ + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + // Create a table + SwInsertTableOptions TableOpt(SwInsertTableFlags::DefaultBorder, 0); + (void)&pWrtShell->InsertTable(TableOpt, 4, 3); + + uno::Reference<text::XTextTablesSupplier> xTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xTableNames = xTablesSupplier->getTextTables(); + CPPUNIT_ASSERT(xTableNames->hasByName(u"Table1"_ustr)); + uno::Reference<text::XTextTable> xTable1(xTableNames->getByName(u"Table1"_ustr), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable1->getColumns()->getCount()); + + // fill table with data + SwXTextDocument* pTextDoc = getSwTextDoc(); + for (int i = 0; i < 3; ++i) + { + pWrtShell->Insert(u"x"_ustr); + pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT); + } + + Scheduler::ProcessEventsToIdle(); + + uno::Reference<text::XTextRange> xCellA1(xTable1->getCellByName(u"A1"_ustr), uno::UNO_QUERY); + xCellA1->setString(u"A1"_ustr); + uno::Reference<text::XTextRange> xCellB1(xTable1->getCellByName(u"B1"_ustr), uno::UNO_QUERY); + xCellB1->setString(u"B1"_ustr); + uno::Reference<text::XTextRange> xCellC1(xTable1->getCellByName(u"C1"_ustr), uno::UNO_QUERY); + xCellC1->setString(u"C1"_ustr); + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPathContent(pXmlDoc, "/root/page/body/tab/row[1]/cell[1]/txt", u"A1"); + assertXPathContent(pXmlDoc, "/root/page/body/tab/row[1]/cell[2]/txt", u"B1"); + assertXPathContent(pXmlDoc, "/root/page/body/tab/row[1]/cell[3]/txt", u"C1"); + + // enable redlining + dispatchCommand(mxComponent, u".uno:TrackChanges"_ustr, {}); + CPPUNIT_ASSERT_MESSAGE("redlining should be on", + pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + + // Move first column of the table before the third column by drag & drop + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + SwFrame* pPage = pLayout->Lower(); + SwFrame* pBody = pPage->GetLower(); + SwFrame* pTable = pBody->GetLower(); + SwFrame* pRow1 = pTable->GetLower(); + SwFrame* pCellA1 = pRow1->GetLower(); + SwFrame* pRow3 = pRow1->GetNext()->GetNext(); + SwFrame* pCellA3 = pRow3->GetLower(); + const SwRect& rCellA1Rect = pCellA1->getFrameArea(); + const SwRect& rCellA3Rect = pCellA3->getFrameArea(); + Point ptTo(rCellA3Rect.Left() + rCellA3Rect.Width() / 2, + rCellA3Rect.Top() + rCellA3Rect.Height() / 2); + // select first table row by using the middle point of the left border of row 1 + Point ptRow(rCellA1Rect.Left() - 5, rCellA1Rect.Top() + rCellA1Rect.Height() / 2); + pWrtShell->SelectTableRowCol(ptRow); + + rtl::Reference<SwTransferable> xTransfer = new SwTransferable(*pWrtShell); + + xTransfer->PrivateDrop(*pWrtShell, ptTo, /*bMove=*/true, /*bXSelection=*/true); + + // reject changes results 4 rows again, not 5 + dispatchCommand(mxComponent, u".uno:RejectAllTrackedChanges"_ustr, {}); + + xTableNames = xTablesSupplier->getTextTables(); + CPPUNIT_ASSERT(xTableNames->hasByName(u"Table1"_ustr)); + uno::Reference<text::XTextTable> xTable2(xTableNames->getByName(u"Table1"_ustr), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable2->getColumns()->getCount()); + // This was 5 (moving row without change tracking) + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), xTable2->getRows()->getCount()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf154599_MovingColumn) +{ + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + // Create a table with less columns than rows + SwInsertTableOptions TableOpt(SwInsertTableFlags::DefaultBorder, 0); + (void)&pWrtShell->InsertTable(TableOpt, 4, 3); + + uno::Reference<text::XTextTablesSupplier> xTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xTableNames = xTablesSupplier->getTextTables(); + CPPUNIT_ASSERT(xTableNames->hasByName(u"Table1"_ustr)); + uno::Reference<text::XTextTable> xTable1(xTableNames->getByName(u"Table1"_ustr), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable1->getColumns()->getCount()); + + // without redlining + CPPUNIT_ASSERT_MESSAGE("redlining should be off", + !pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + + // Move first column of the table before the third column by drag & drop + + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + SwFrame* pPage = pLayout->Lower(); + SwFrame* pBody = pPage->GetLower(); + SwFrame* pTable = pBody->GetLower(); + SwFrame* pRow1 = pTable->GetLower(); + SwFrame* pCellA1 = pRow1->GetLower(); + SwFrame* pCellC1 = pCellA1->GetNext()->GetNext(); + const SwRect& rCellA1Rect = pCellA1->getFrameArea(); + const SwRect& rCellC1Rect = pCellC1->getFrameArea(); + Point ptTo(rCellC1Rect.Left() + rCellC1Rect.Width() / 2, + rCellC1Rect.Top() + rCellC1Rect.Height() / 2); + // select first table column by using the middle point of the top border of column A + Point ptColumn(rCellA1Rect.Left() + rCellA1Rect.Width() / 2, rCellA1Rect.Top() - 5); + pWrtShell->SelectTableRowCol(ptColumn); + + // This crashed here before the fix. + rtl::Reference<SwTransferable> xTransfer = new SwTransferable(*pWrtShell); + + xTransfer->PrivateDrop(*pWrtShell, ptTo, /*bMove=*/true, /*bXSelection=*/true); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable1->getColumns()->getCount()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf155846_MovingColumn) +{ + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + // Create a table + SwInsertTableOptions TableOpt(SwInsertTableFlags::DefaultBorder, 0); + (void)&pWrtShell->InsertTable(TableOpt, 4, 3); + + uno::Reference<text::XTextTablesSupplier> xTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xTableNames = xTablesSupplier->getTextTables(); + CPPUNIT_ASSERT(xTableNames->hasByName(u"Table1"_ustr)); + uno::Reference<text::XTextTable> xTable1(xTableNames->getByName(u"Table1"_ustr), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable1->getColumns()->getCount()); + + // fill table with data + SwXTextDocument* pTextDoc = getSwTextDoc(); + for (int i = 0; i < 4; ++i) + { + pWrtShell->Insert(u"x"_ustr); + pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN); + } + + Scheduler::ProcessEventsToIdle(); + + uno::Reference<text::XTextRange> xCellA1(xTable1->getCellByName(u"A1"_ustr), uno::UNO_QUERY); + xCellA1->setString(u"A1"_ustr); + uno::Reference<text::XTextRange> xCellA2(xTable1->getCellByName(u"A2"_ustr), uno::UNO_QUERY); + xCellA2->setString(u"A2"_ustr); + uno::Reference<text::XTextRange> xCellA3(xTable1->getCellByName(u"A3"_ustr), uno::UNO_QUERY); + xCellA3->setString(u"A3"_ustr); + uno::Reference<text::XTextRange> xCellA4(xTable1->getCellByName(u"A4"_ustr), uno::UNO_QUERY); + xCellA4->setString(u"A4"_ustr); + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPathContent(pXmlDoc, "/root/page/body/tab/row[1]/cell[1]/txt", u"A1"); + assertXPathContent(pXmlDoc, "/root/page/body/tab/row[2]/cell[1]/txt", u"A2"); + assertXPathContent(pXmlDoc, "/root/page/body/tab/row[3]/cell[1]/txt", u"A3"); + assertXPathContent(pXmlDoc, "/root/page/body/tab/row[4]/cell[1]/txt", u"A4"); + + // enable redlining + dispatchCommand(mxComponent, u".uno:TrackChanges"_ustr, {}); + CPPUNIT_ASSERT_MESSAGE("redlining should be on", + pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + + // Move first column of the table before the third column by drag & drop + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + SwFrame* pPage = pLayout->Lower(); + SwFrame* pBody = pPage->GetLower(); + SwFrame* pTable = pBody->GetLower(); + SwFrame* pRow1 = pTable->GetLower(); + SwFrame* pCellA1 = pRow1->GetLower(); + SwFrame* pCellC1 = pCellA1->GetNext()->GetNext(); + const SwRect& rCellA1Rect = pCellA1->getFrameArea(); + const SwRect& rCellC1Rect = pCellC1->getFrameArea(); + Point ptTo(rCellC1Rect.Left() + rCellC1Rect.Width() / 2, + rCellC1Rect.Top() + rCellC1Rect.Height() / 2); + // select first table column by using the middle point of the top border of column A + Point ptColumn(rCellA1Rect.Left() + rCellA1Rect.Width() / 2, rCellA1Rect.Top() - 5); + pWrtShell->SelectTableRowCol(ptColumn); + + // This crashed here before the fix. + rtl::Reference<SwTransferable> xTransfer = new SwTransferable(*pWrtShell); + + xTransfer->PrivateDrop(*pWrtShell, ptTo, /*bMove=*/true, /*bXSelection=*/true); + + // reject changes results 3 columns again, not 4 + dispatchCommand(mxComponent, u".uno:RejectAllTrackedChanges"_ustr, {}); + + xTableNames = xTablesSupplier->getTextTables(); + CPPUNIT_ASSERT(xTableNames->hasByName(u"Table1"_ustr)); + uno::Reference<text::XTextTable> xTable2(xTableNames->getByName(u"Table1"_ustr), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), xTable2->getRows()->getCount()); + // This was 4 (moving column without change tracking) + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable2->getColumns()->getCount()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf154771_MovingMultipleColumns) +{ + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + // Create a table with less columns than rows + SwInsertTableOptions TableOpt(SwInsertTableFlags::DefaultBorder, 0); + (void)&pWrtShell->InsertTable(TableOpt, 5, 4); + + uno::Reference<text::XTextTablesSupplier> xTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xTableNames = xTablesSupplier->getTextTables(); + CPPUNIT_ASSERT(xTableNames->hasByName(u"Table1"_ustr)); + uno::Reference<text::XTextTable> xTable1(xTableNames->getByName(u"Table1"_ustr), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(5), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), xTable1->getColumns()->getCount()); + + // without redlining + CPPUNIT_ASSERT_MESSAGE("redlining should be off", + !pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + + // Move first two columns of the table before column D by drag & drop + + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + SwFrame* pPage = pLayout->Lower(); + SwFrame* pBody = pPage->GetLower(); + SwFrame* pTable = pBody->GetLower(); + SwFrame* pRow1 = pTable->GetLower(); + SwFrame* pCellA1 = pRow1->GetLower(); + SwFrame* pCellB1 = pCellA1->GetNext(); + SwFrame* pCellD1 = pCellB1->GetNext()->GetNext(); + const SwRect& rCellA1Rect = pCellA1->getFrameArea(); + const SwRect& rCellB1Rect = pCellB1->getFrameArea(); + const SwRect& rCellD1Rect = pCellD1->getFrameArea(); + Point ptTo(rCellD1Rect.Left() + rCellD1Rect.Width() / 2, + rCellD1Rect.Top() + rCellD1Rect.Height() / 2); + // select first two table columns by using + // the middle point of the top border of column A + // and middle point of the top border of column B + Point ptColumnA(rCellA1Rect.Left() + rCellA1Rect.Width() / 2, rCellA1Rect.Top() - 5); + const Point ptColumnB(rCellB1Rect.Left() + rCellB1Rect.Width() / 2, rCellB1Rect.Top() - 5); + pWrtShell->SelectTableRowCol(ptColumnA, &ptColumnB); + + rtl::Reference<SwTransferable> xTransfer = new SwTransferable(*pWrtShell); + xTransfer->PrivateDrop(*pWrtShell, ptTo, /*bMove=*/true, /*bXSelection=*/true); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(5), xTable1->getRows()->getCount()); + // This was 5 before the fix (only the first selected column was moved, the + // other ones were copied instead of moving) + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), xTable1->getColumns()->getCount()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf44773) +{ + // allow resizing table rows, if cursor outside the table + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + // insert an empty paragraph + pWrtShell->SplitNode(); + + // create a table + SwInsertTableOptions TableOpt(SwInsertTableFlags::DefaultBorder, 0); + (void)&pWrtShell->InsertTable(TableOpt, 2, 1); + + // the cursor is not inside the table + CPPUNIT_ASSERT(!pWrtShell->IsCursorInTable()); + + uno::Reference<text::XTextTablesSupplier> xTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xTableNames = xTablesSupplier->getTextTables(); + CPPUNIT_ASSERT(xTableNames->hasByName(u"Table1"_ustr)); + uno::Reference<text::XTextTable> xTable1(xTableNames->getByName(u"Table1"_ustr), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTable1->getColumns()->getCount()); + + Scheduler::ProcessEventsToIdle(); + + // set table row height by drag & drop + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + + SwFrame* pPage = pLayout->Lower(); + SwFrame* pBody = pPage->GetLower(); + SwFrame* pTable = pBody->GetLower()->GetNext(); + SwFrame* pRow1 = pTable->GetLower(); + CPPUNIT_ASSERT(pRow1->IsRowFrame()); + SwFrame* pCellA1 = pRow1->GetLower(); + const SwRect& rCellA1Rect = pCellA1->getFrameArea(); + auto nRowHeight = rCellA1Rect.Height(); + // select center of the bottom border of the first table cell + Point ptFrom(rCellA1Rect.Left() + rCellA1Rect.Width() / 2, rCellA1Rect.Top() + nRowHeight); + // double the row height + Point ptTo(rCellA1Rect.Left() + rCellA1Rect.Width() / 2, rCellA1Rect.Top() + 2 * nRowHeight); + vcl::Window& rEditWin = getSwDocShell()->GetView()->GetEditWin(); + Point aFrom = rEditWin.LogicToPixel(ptFrom); + MouseEvent aClickEvent(aFrom, 1, MouseEventModifiers::SIMPLECLICK | MouseEventModifiers::SELECT, + MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent); + Point aTo = rEditWin.LogicToPixel(ptTo); + MouseEvent aMoveEvent(aTo, 1, MouseEventModifiers::SIMPLECLICK | MouseEventModifiers::SELECT, + MOUSE_LEFT); + TrackingEvent aTEvt(aMoveEvent, TrackingEventFlags::Repeat); + // drag & drop of cell border inside the document (and outside the table) + // still based on the ruler code, use that to simulate dragging + getSwDocShell()->GetView()->GetVRuler().Tracking(aTEvt); + TrackingEvent aTEvt2(aMoveEvent, TrackingEventFlags::End); + getSwDocShell()->GetView()->GetVRuler().Tracking(aTEvt2); + Scheduler::ProcessEventsToIdle(); + rEditWin.CaptureMouse(); + rEditWin.ReleaseMouse(); + + // this was 396 (not modified row height previously) + CPPUNIT_ASSERT_GREATER(tools::Long(750), pCellA1->getFrameArea().Height()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf157833) +{ + // allow resizing table rows & columns using a minimal hit area + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + // insert an empty paragraph + pWrtShell->SplitNode(); + + // create a table + SwInsertTableOptions TableOpt(SwInsertTableFlags::DefaultBorder, 0); + (void)&pWrtShell->InsertTable(TableOpt, 2, 1); + + // the cursor is not inside the table + CPPUNIT_ASSERT(!pWrtShell->IsCursorInTable()); + + uno::Reference<text::XTextTablesSupplier> xTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xTableNames = xTablesSupplier->getTextTables(); + CPPUNIT_ASSERT(xTableNames->hasByName(u"Table1"_ustr)); + uno::Reference<text::XTextTable> xTable1(xTableNames->getByName(u"Table1"_ustr), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTable1->getColumns()->getCount()); + + Scheduler::ProcessEventsToIdle(); + + // set table row height by drag & drop + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + + SwFrame* pPage = pLayout->Lower(); + SwFrame* pBody = pPage->GetLower(); + SwFrame* pTable = pBody->GetLower()->GetNext(); + SwFrame* pRow1 = pTable->GetLower(); + CPPUNIT_ASSERT(pRow1->IsRowFrame()); + SwFrame* pCellA1 = pRow1->GetLower(); + const SwRect& rCellA1Rect = pCellA1->getFrameArea(); + auto nRowHeight = rCellA1Rect.Height(); + // select center of the bottom border of the first table cell + Point ptFrom(rCellA1Rect.Left() + rCellA1Rect.Width() / 2, rCellA1Rect.Top() + nRowHeight); + // double the row height + Point ptTo(rCellA1Rect.Left() + rCellA1Rect.Width() / 2, rCellA1Rect.Top() + 2 * nRowHeight); + vcl::Window& rEditWin = getSwDocShell()->GetView()->GetEditWin(); + Point aFrom = rEditWin.LogicToPixel(ptFrom); + Point aArea(aFrom.X(), aFrom.Y() + 2); + MouseEvent aClickEvent(aArea, 1, MouseEventModifiers::SIMPLECLICK | MouseEventModifiers::SELECT, + MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent); + Point aTo = rEditWin.LogicToPixel(ptTo); + MouseEvent aMoveEvent(aTo, 1, MouseEventModifiers::SIMPLECLICK | MouseEventModifiers::SELECT, + MOUSE_LEFT); + TrackingEvent aTEvt(aMoveEvent, TrackingEventFlags::Repeat); + // drag & drop of cell border inside the document (and outside the table) + // still based on the ruler code, use that to simulate dragging + getSwDocShell()->GetView()->GetVRuler().Tracking(aTEvt); + TrackingEvent aTEvt2(aMoveEvent, TrackingEventFlags::End); + getSwDocShell()->GetView()->GetVRuler().Tracking(aTEvt2); + Scheduler::ProcessEventsToIdle(); + rEditWin.CaptureMouse(); + rEditWin.ReleaseMouse(); + + // this was 396 (not modified row height previously, when clicking only in a 2-pixel distance + // from the center of the border) + CPPUNIT_ASSERT_GREATER(tools::Long(750), pCellA1->getFrameArea().Height()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf155692) +{ + // allow resizing table rows & columns using a normal hit area + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + // insert an empty paragraph + pWrtShell->SplitNode(); + + // create a table + SwInsertTableOptions TableOpt(SwInsertTableFlags::DefaultBorder, 0); + (void)&pWrtShell->InsertTable(TableOpt, 2, 1); + + // the cursor is not inside the table + CPPUNIT_ASSERT(!pWrtShell->IsCursorInTable()); + + uno::Reference<text::XTextTablesSupplier> xTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xTableNames = xTablesSupplier->getTextTables(); + CPPUNIT_ASSERT(xTableNames->hasByName(u"Table1"_ustr)); + uno::Reference<text::XTextTable> xTable1(xTableNames->getByName(u"Table1"_ustr), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTable1->getColumns()->getCount()); + + Scheduler::ProcessEventsToIdle(); + + // set table row height by drag & drop + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + + SwFrame* pPage = pLayout->Lower(); + SwFrame* pBody = pPage->GetLower(); + SwFrame* pTable = pBody->GetLower()->GetNext(); + SwFrame* pRow1 = pTable->GetLower(); + CPPUNIT_ASSERT(pRow1->IsRowFrame()); + SwFrame* pCellA1 = pRow1->GetLower(); + const SwRect& rCellA1Rect = pCellA1->getFrameArea(); + auto nRowHeight = rCellA1Rect.Height(); + // select center of the bottom border of the first table cell + Point ptFrom(rCellA1Rect.Left() + rCellA1Rect.Width() / 2, rCellA1Rect.Top() + nRowHeight); + // double the row height + Point ptTo(rCellA1Rect.Left() + rCellA1Rect.Width() / 2, rCellA1Rect.Top() + 2 * nRowHeight); + vcl::Window& rEditWin = getSwDocShell()->GetView()->GetEditWin(); + Point aFrom = rEditWin.LogicToPixel(ptFrom); + Point aArea(aFrom.X(), aFrom.Y() + 5); + MouseEvent aClickEvent(aArea, 1, MouseEventModifiers::SIMPLECLICK | MouseEventModifiers::SELECT, + MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent); + Point aTo = rEditWin.LogicToPixel(ptTo); + MouseEvent aMoveEvent(aTo, 1, MouseEventModifiers::SIMPLECLICK | MouseEventModifiers::SELECT, + MOUSE_LEFT); + TrackingEvent aTEvt(aMoveEvent, TrackingEventFlags::Repeat); + // drag & drop of cell border inside the document (and outside the table) + // still based on the ruler code, use that to simulate dragging + getSwDocShell()->GetView()->GetVRuler().Tracking(aTEvt); + TrackingEvent aTEvt2(aMoveEvent, TrackingEventFlags::End); + getSwDocShell()->GetView()->GetVRuler().Tracking(aTEvt2); + Scheduler::ProcessEventsToIdle(); + rEditWin.CaptureMouse(); + rEditWin.ReleaseMouse(); + + // this was 396 (not modified row height previously, when clicking only in a 5-pixel distance + // from the center of the border) + CPPUNIT_ASSERT_GREATER(tools::Long(750), pCellA1->getFrameArea().Height()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf160842) +{ + createSwDoc("tdf160842.fodt"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + // the cursor is not in the table + CPPUNIT_ASSERT(!pWrtShell->IsCursorInTable()); + + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + auto pPage = dynamic_cast<SwPageFrame*>(pLayout->Lower()); + CPPUNIT_ASSERT(pPage); + const SwSortedObjs& rPageObjs = *pPage->GetSortedObjs(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPageObjs.size()); + auto pPageFly = dynamic_cast<SwFlyAtContentFrame*>(rPageObjs[0]); + CPPUNIT_ASSERT(pPageFly); + auto pTable = dynamic_cast<SwTabFrame*>(pPageFly->GetLower()); + CPPUNIT_ASSERT(pTable); + auto pRow2 = pTable->GetLower()->GetNext(); + CPPUNIT_ASSERT(pRow2->IsRowFrame()); + auto pCellA2 = pRow2->GetLower(); + CPPUNIT_ASSERT(pCellA2); + const SwRect& rCellA2Rect = pCellA2->getFrameArea(); + auto nRowHeight = rCellA2Rect.Height(); + // select center of the bottom cell + Point ptFrom(rCellA2Rect.Left() + rCellA2Rect.Width() / 2, rCellA2Rect.Top() + nRowHeight / 2); + vcl::Window& rEditWin = getSwDocShell()->GetView()->GetEditWin(); + Point aFrom = rEditWin.LogicToPixel(ptFrom); + MouseEvent aClickEvent(aFrom, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent); + rEditWin.MouseButtonUp(aClickEvent); + + // the cursor is in the table + CPPUNIT_ASSERT(pWrtShell->IsCursorInTable()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf160836) +{ + createSwDoc("tdf160842.fodt"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + // set table row height by drag & drop at images cropped by the fixed row height + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + auto pPage = dynamic_cast<SwPageFrame*>(pLayout->Lower()); + CPPUNIT_ASSERT(pPage); + const SwSortedObjs& rPageObjs = *pPage->GetSortedObjs(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPageObjs.size()); + auto pPageFly = dynamic_cast<SwFlyAtContentFrame*>(rPageObjs[0]); + CPPUNIT_ASSERT(pPageFly); + auto pTable = dynamic_cast<SwTabFrame*>(pPageFly->GetLower()); + CPPUNIT_ASSERT(pTable); + auto pRow1 = pTable->GetLower(); + CPPUNIT_ASSERT(pRow1->IsRowFrame()); + auto pCellA1 = pRow1->GetLower(); + CPPUNIT_ASSERT(pCellA1); + const SwRect& rCellA1Rect = pCellA1->getFrameArea(); + auto nRowHeight = rCellA1Rect.Height(); + // select center of the bottom border of the first table cell + Point ptFrom(rCellA1Rect.Left() + rCellA1Rect.Width() / 2, rCellA1Rect.Top() + nRowHeight); + // halve the row height + Point ptTo(rCellA1Rect.Left() + rCellA1Rect.Width() / 2, rCellA1Rect.Top() + 0.5 * nRowHeight); + vcl::Window& rEditWin = getSwDocShell()->GetView()->GetEditWin(); + Point aFrom = rEditWin.LogicToPixel(ptFrom); + MouseEvent aClickEvent(aFrom, 1, MouseEventModifiers::SIMPLECLICK | MouseEventModifiers::SELECT, + MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent); + Point aTo = rEditWin.LogicToPixel(ptTo); + MouseEvent aMoveEvent(aTo, 1, MouseEventModifiers::SIMPLECLICK | MouseEventModifiers::SELECT, + MOUSE_LEFT); + TrackingEvent aTEvt(aMoveEvent, TrackingEventFlags::Repeat); + // drag & drop of cell border inside the document (and outside the table) + // still based on the ruler code, use that to simulate dragging + getSwDocShell()->GetView()->GetVRuler().Tracking(aTEvt); + TrackingEvent aTEvt2(aMoveEvent, TrackingEventFlags::End); + getSwDocShell()->GetView()->GetVRuler().Tracking(aTEvt2); + Scheduler::ProcessEventsToIdle(); + rEditWin.CaptureMouse(); + rEditWin.ReleaseMouse(); + + // this was 3910 (not modified row height previously) + CPPUNIT_ASSERT_LESS(tools::Long(2000), pCellA1->getFrameArea().Height()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf161261) +{ + createSwDoc("tdf160842.fodt"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + // the cursor is not in the table + CPPUNIT_ASSERT(!pWrtShell->IsCursorInTable()); + + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + auto pPage = dynamic_cast<SwPageFrame*>(pLayout->Lower()); + CPPUNIT_ASSERT(pPage); + const SwSortedObjs& rPageObjs = *pPage->GetSortedObjs(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPageObjs.size()); + auto pPageFly = dynamic_cast<SwFlyAtContentFrame*>(rPageObjs[0]); + CPPUNIT_ASSERT(pPageFly); + auto pTable = dynamic_cast<SwTabFrame*>(pPageFly->GetLower()); + CPPUNIT_ASSERT(pTable); + auto pRow1 = pTable->GetLower(); + CPPUNIT_ASSERT(pRow1->IsRowFrame()); + auto pCellA1 = pRow1->GetLower(); + CPPUNIT_ASSERT(pCellA1); + const SwRect& rCellA1Rect = pCellA1->getFrameArea(); + auto nRowHeight = rCellA1Rect.Height(); + + // select image by clicking on it at the center of the upper cell + Point ptFrom(rCellA1Rect.Left() + rCellA1Rect.Width() / 2, rCellA1Rect.Top() + nRowHeight / 2); + vcl::Window& rEditWin = getSwDocShell()->GetView()->GetEditWin(); + Point aFrom = rEditWin.LogicToPixel(ptFrom); + MouseEvent aClickEvent(aFrom, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent); + rEditWin.MouseButtonUp(aClickEvent); + + // Then make sure that the image is selected: + SelectionType eType = pWrtShell->GetSelectionType(); + CPPUNIT_ASSERT_EQUAL(SelectionType::Graphic, eType); + + uno::Reference<drawing::XShape> xShape = getShape(2); + CPPUNIT_ASSERT(xShape.is()); + + // zoom image by drag & drop using right bottom handle of the image + const SwRect& rSelRect = pWrtShell->GetAnyCurRect(CurRectType::Frame); + Point ptFromHandle(rSelRect.Right(), rSelRect.Bottom()); + Point aFromHandle = rEditWin.LogicToPixel(ptFromHandle); + Point ptTo(rSelRect.Left() + rSelRect.Width() * 1.5, rSelRect.Top() + rSelRect.Height() * 1.5); + Point aTo = rEditWin.LogicToPixel(ptTo); + MouseEvent aClickEvent2(aFromHandle, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent2); + MouseEvent aClickEvent3(aTo, 0, MouseEventModifiers::SIMPLEMOVE, MOUSE_LEFT); + rEditWin.MouseMove(aClickEvent3); + rEditWin.MouseMove(aClickEvent3); + MouseEvent aClickEvent4(aTo, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + rEditWin.MouseButtonUp(aClickEvent4); + Scheduler::ProcessEventsToIdle(); + + // Make sure image is greater than before, instead of minimizing it to the cell size + // This was 8707 and 6509 + CPPUNIT_ASSERT_GREATER(sal_Int32(10000), xShape->getSize().Width); + CPPUNIT_ASSERT_GREATER(sal_Int32(8000), xShape->getSize().Height); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf161332) +{ + createSwDoc("tdf160842.fodt"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + // the cursor is not in the table + CPPUNIT_ASSERT(!pWrtShell->IsCursorInTable()); + + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + auto pPage = dynamic_cast<SwPageFrame*>(pLayout->Lower()); + CPPUNIT_ASSERT(pPage); + const SwSortedObjs& rPageObjs = *pPage->GetSortedObjs(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPageObjs.size()); + auto pPageFly = dynamic_cast<SwFlyAtContentFrame*>(rPageObjs[0]); + CPPUNIT_ASSERT(pPageFly); + auto pTable = dynamic_cast<SwTabFrame*>(pPageFly->GetLower()); + CPPUNIT_ASSERT(pTable); + auto pRow1 = pTable->GetLower(); + CPPUNIT_ASSERT(pRow1->IsRowFrame()); + auto pCellA1 = pRow1->GetLower(); + CPPUNIT_ASSERT(pCellA1); + const SwRect& rCellA1Rect = pCellA1->getFrameArea(); + auto nRowHeight = rCellA1Rect.Height(); + + // select text frame by clicking on it at the right side of the upper cell + Point ptFrom(rCellA1Rect.Left() + rCellA1Rect.Width(), rCellA1Rect.Top() + nRowHeight / 2); + vcl::Window& rEditWin = getSwDocShell()->GetView()->GetEditWin(); + Point aFrom = rEditWin.LogicToPixel(ptFrom); + MouseEvent aClickEvent(aFrom, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent); + rEditWin.MouseButtonUp(aClickEvent); + + // Then make sure that the text frame is selected: + SelectionType eType = pWrtShell->GetSelectionType(); + // This was false (SelectionType::Graphic) + CPPUNIT_ASSERT_EQUAL(SelectionType::Frame, eType); + + // remove selection + dispatchCommand(mxComponent, u".uno:Escape"_ustr, {}); + + // select text frame by clicking on it at the right side of the bottom cell + auto pRow2 = pRow1->GetNext(); + CPPUNIT_ASSERT(pRow2->IsRowFrame()); + auto pCellA2 = pRow2->GetLower(); + CPPUNIT_ASSERT(pCellA2); + const SwRect& rCellA2Rect = pCellA2->getFrameArea(); + auto nRow2Height = rCellA2Rect.Height(); + Point ptFrom2(rCellA2Rect.Left() + rCellA2Rect.Width(), rCellA2Rect.Top() + nRow2Height / 2); + Point aFrom2 = rEditWin.LogicToPixel(ptFrom2); + MouseEvent aClickEvent2(aFrom2, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent2); + rEditWin.MouseButtonUp(aClickEvent2); + + // Then make sure that the text frame is selected: + SelectionType eType2 = pWrtShell->GetSelectionType(); + // This was false (SelectionType::Graphic) + CPPUNIT_ASSERT_EQUAL(SelectionType::Frame, eType2); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf161426) +{ + createSwDoc("tdf161426.fodt"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + // the cursor is not in the table + CPPUNIT_ASSERT(!pWrtShell->IsCursorInTable()); + + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + auto pPage = dynamic_cast<SwPageFrame*>(pLayout->Lower()); + CPPUNIT_ASSERT(pPage); + const SwSortedObjs& rPageObjs = *pPage->GetSortedObjs(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPageObjs.size()); + auto pPageFly = dynamic_cast<SwFlyAtContentFrame*>(rPageObjs[0]); + CPPUNIT_ASSERT(pPageFly); + auto pTable = dynamic_cast<SwTabFrame*>(pPageFly->GetLower()); + CPPUNIT_ASSERT(pTable); + auto pRow1 = pTable->GetLower(); + CPPUNIT_ASSERT(pRow1->IsRowFrame()); + auto pCellA1 = pRow1->GetLower(); + CPPUNIT_ASSERT(pCellA1); + auto pCellB1 = pCellA1->GetNext(); + const SwRect& rCellB1Rect = pCellB1->getFrameArea(); + auto nRowHeight = rCellB1Rect.Height(); + + // select text frame by clicking on it at the right side of the upper right cell + Point ptFrom(rCellB1Rect.Left() + rCellB1Rect.Width(), rCellB1Rect.Top() + nRowHeight / 2); + vcl::Window& rEditWin = getSwDocShell()->GetView()->GetEditWin(); + Point aFrom = rEditWin.LogicToPixel(ptFrom); + MouseEvent aClickEvent(aFrom, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent); + rEditWin.MouseButtonUp(aClickEvent); + + // Then make sure that the text frame is selected: + SelectionType eType = pWrtShell->GetSelectionType(); + CPPUNIT_ASSERT_EQUAL(SelectionType::Frame, eType); + + // remove selection + dispatchCommand(mxComponent, u".uno:Escape"_ustr, {}); + + // select text frame by clicking on it at the right side of the bottom right cell + auto pRow2 = pRow1->GetNext(); + CPPUNIT_ASSERT(pRow2->IsRowFrame()); + auto pCellA2 = pRow2->GetLower(); + CPPUNIT_ASSERT(pCellA2); + auto pCellB2 = pCellA2->GetNext(); + CPPUNIT_ASSERT(pCellB2); + const SwRect& rCellB2Rect = pCellB2->getFrameArea(); + auto nRow2Height = rCellB2Rect.Height(); + Point ptFrom2(rCellB2Rect.Left() + rCellB2Rect.Width(), rCellB2Rect.Top() + nRow2Height / 2); + Point aFrom2 = rEditWin.LogicToPixel(ptFrom2); + MouseEvent aClickEvent2(aFrom2, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent2); + rEditWin.MouseButtonUp(aClickEvent2); + + // Then make sure that the text frame is selected: + SelectionType eType2 = pWrtShell->GetSelectionType(); + CPPUNIT_ASSERT_EQUAL(SelectionType::Frame, eType2); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf161426_content) +{ + createSwDoc("tdf161426.fodt"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + // the cursor is not in the table + CPPUNIT_ASSERT(!pWrtShell->IsCursorInTable()); + + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + auto pPage = dynamic_cast<SwPageFrame*>(pLayout->Lower()); + CPPUNIT_ASSERT(pPage); + const SwSortedObjs& rPageObjs = *pPage->GetSortedObjs(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPageObjs.size()); + auto pPageFly = dynamic_cast<SwFlyAtContentFrame*>(rPageObjs[0]); + CPPUNIT_ASSERT(pPageFly); + auto pTable = dynamic_cast<SwTabFrame*>(pPageFly->GetLower()); + CPPUNIT_ASSERT(pTable); + auto pRow1 = pTable->GetLower(); + CPPUNIT_ASSERT(pRow1->IsRowFrame()); + auto pCellA1 = pRow1->GetLower(); + CPPUNIT_ASSERT(pCellA1); + auto pCellB1 = pCellA1->GetNext(); + const SwRect& rCellB1Rect = pCellB1->getFrameArea(); + auto nRowHeight = rCellB1Rect.Height(); + + // select content of the B1 by clicking on the center of it + Point ptFrom(rCellB1Rect.Left() + rCellB1Rect.Width() / 2, rCellB1Rect.Top() + nRowHeight / 2); + vcl::Window& rEditWin = getSwDocShell()->GetView()->GetEditWin(); + Point aFrom = rEditWin.LogicToPixel(ptFrom); + MouseEvent aClickEvent(aFrom, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent); + rEditWin.MouseButtonUp(aClickEvent); + + // Then make sure that the cursor in the table: + SelectionType eType2 = pWrtShell->GetSelectionType(); + // This was false + bool bCursorInTable = eType2 == (SelectionType::Text | SelectionType::Table); + CPPUNIT_ASSERT(bCursorInTable); + + // select content of the B2 by clicking on the center of it + auto pRow2 = pRow1->GetNext(); + CPPUNIT_ASSERT(pRow2->IsRowFrame()); + auto pCellA2 = pRow2->GetLower(); + CPPUNIT_ASSERT(pCellA2); + auto pCellB2 = pCellA2->GetNext(); + CPPUNIT_ASSERT(pCellB2); + const SwRect& rCellB2Rect = pCellB2->getFrameArea(); + auto nRow2Height = rCellB2Rect.Height(); + Point ptFrom2(rCellB2Rect.Left() + rCellB2Rect.Width() / 2, + rCellB2Rect.Top() + nRow2Height / 2); + Point aFrom2 = rEditWin.LogicToPixel(ptFrom2); + MouseEvent aClickEvent2(aFrom2, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent2); + rEditWin.MouseButtonUp(aClickEvent2); + + // Then make sure that the cursor in the table: + SelectionType eType3 = pWrtShell->GetSelectionType(); + // This was false + bCursorInTable = eType3 == (SelectionType::Text | SelectionType::Table); + CPPUNIT_ASSERT(bCursorInTable); + + // select content of the A2 by clicking on the center of it + const SwRect& rCellA2Rect = pCellA2->getFrameArea(); + Point ptFrom3(rCellA2Rect.Left() + rCellA2Rect.Width() / 2, + rCellA2Rect.Top() + nRow2Height / 2); + Point aFrom3 = rEditWin.LogicToPixel(ptFrom3); + MouseEvent aClickEvent3(aFrom3, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent3); + rEditWin.MouseButtonUp(aClickEvent3); + + // Then make sure that the cursor in the table: + SelectionType eType4 = pWrtShell->GetSelectionType(); + // This was false + bCursorInTable = eType4 == (SelectionType::Text | SelectionType::Table); + CPPUNIT_ASSERT(bCursorInTable); + + // select content of the A1 by clicking on the center of it + const SwRect& rCellA1Rect = pCellA1->getFrameArea(); + auto nRow1Height = rCellA1Rect.Height(); + Point ptFrom4(rCellA1Rect.Left() + rCellA1Rect.Width() / 2, + rCellA1Rect.Top() + nRow1Height / 2); + Point aFrom4 = rEditWin.LogicToPixel(ptFrom4); + MouseEvent aClickEvent4(aFrom4, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent4); + rEditWin.MouseButtonUp(aClickEvent4); + + // Then make sure that the text frame is selected: + SelectionType eType5 = pWrtShell->GetSelectionType(); + CPPUNIT_ASSERT_EQUAL(SelectionType::Graphic, eType5); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf161360) +{ + createSwDoc("tdf160842.fodt"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + // the cursor is not in the table + CPPUNIT_ASSERT(!pWrtShell->IsCursorInTable()); + + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + auto pPage = dynamic_cast<SwPageFrame*>(pLayout->Lower()); + CPPUNIT_ASSERT(pPage); + const SwSortedObjs& rPageObjs = *pPage->GetSortedObjs(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPageObjs.size()); + auto pPageFly = dynamic_cast<SwFlyAtContentFrame*>(rPageObjs[0]); + CPPUNIT_ASSERT(pPageFly); + auto pTable = dynamic_cast<SwTabFrame*>(pPageFly->GetLower()); + CPPUNIT_ASSERT(pTable); + auto pRow1 = pTable->GetLower(); + CPPUNIT_ASSERT(pRow1->IsRowFrame()); + auto pCellA1 = pRow1->GetLower(); + CPPUNIT_ASSERT(pCellA1); + const SwRect& rCellA1Rect = pCellA1->getFrameArea(); + auto nRowHeight = rCellA1Rect.Height(); + + // select image by clicking on it at the center of the upper cell + Point ptFrom(rCellA1Rect.Left() + rCellA1Rect.Width() / 2, rCellA1Rect.Top() + nRowHeight / 2); + vcl::Window& rEditWin = getSwDocShell()->GetView()->GetEditWin(); + Point aFrom = rEditWin.LogicToPixel(ptFrom); + MouseEvent aClickEvent(aFrom, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + rEditWin.MouseButtonDown(aClickEvent); + rEditWin.MouseButtonUp(aClickEvent); + + // Then make sure that the image is selected: + SelectionType eType = pWrtShell->GetSelectionType(); + CPPUNIT_ASSERT_EQUAL(SelectionType::Graphic, eType); + + // select the text frame instead of the image + // by pressing Escape + dispatchCommand(mxComponent, u".uno:Escape"_ustr, {}); + + // Then make sure that the cursor in the table: + SelectionType eType2 = pWrtShell->GetSelectionType(); + // This was false (only SelectionType::Text) + bool bCursorInTable = eType2 == (SelectionType::Text | SelectionType::Table); + CPPUNIT_ASSERT(bCursorInTable); + + // select the text frame by pressing Escape again + dispatchCommand(mxComponent, u".uno:Escape"_ustr, {}); + + eType2 = pWrtShell->GetSelectionType(); + CPPUNIT_ASSERT_EQUAL(SelectionType::Frame, eType2); + + // deselect the text frame by pressing Escape again + dispatchCommand(mxComponent, u".uno:Escape"_ustr, {}); + + eType2 = pWrtShell->GetSelectionType(); + // The text cursor is after the floating table + CPPUNIT_ASSERT_EQUAL(SelectionType::Text, eType2); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf157533) +{ + // load a table with objects positioned at beginning of text lines + createSwDoc("tdf157533.fodt"); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + auto pShell = getSwDocShell()->GetFEShell(); + CPPUNIT_ASSERT(pShell); + + auto xModel = mxComponent.queryThrow<frame::XModel>(); + uno::Reference<drawing::XShape> xShape(getShapeByName(u"Objet2")); + uno::Reference<view::XSelectionSupplier> xCtrl(xModel->getCurrentController(), uno::UNO_QUERY); + xCtrl->select(uno::Any(xShape)); + + dispatchCommand(mxComponent, u".uno:Escape"_ustr, {}); + + // Then make sure that the cursor in the table: + SelectionType eType2 = pWrtShell->GetSelectionType(); + // This was false (only SelectionType::Text) + bool bCursorInTable = eType2 == (SelectionType::Text | SelectionType::Table); + CPPUNIT_ASSERT(bCursorInTable); + + SwTextNode* pTextNode = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); + // This was false (not in the same paragraph and cell) + CPPUNIT_ASSERT(pTextNode->GetText().indexOf("and the second formula") > -1); + + uno::Reference<drawing::XShape> xShape2(getShapeByName(u"Objet11")); + xCtrl->select(uno::Any(xShape2)); + + dispatchCommand(mxComponent, u".uno:Escape"_ustr, {}); + + SwTextNode* pTextNode2 = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); + // This was false (lost text cursor inside the frame of the formula) + CPPUNIT_ASSERT(pTextNode2->GetTableBox()); + SwTableNode* pTableNode = pWrtShell->GetCursor()->GetPointNode().FindTableNode(); + SwTable& rTable = pTableNode->GetTable(); + // cursor in the same cell + bool bSameBox = pTextNode2->GetTableBox() == rTable.GetTableBox(u"A1"_ustr); + CPPUNIT_ASSERT(bSameBox); + + uno::Reference<drawing::XShape> xShape3(getShapeByName(u"Objet10")); + xCtrl->select(uno::Any(xShape3)); + + dispatchCommand(mxComponent, u".uno:Escape"_ustr, {}); + + SwTextNode* pTextNode3 = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); + // This was false (lost text cursor inside the frame of the formula) + CPPUNIT_ASSERT(pTextNode3->GetTableBox()); + // cursor in the same cell + bSameBox = pTextNode3->GetTableBox() == rTable.GetTableBox(u"B1"_ustr); + CPPUNIT_ASSERT(bSameBox); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf115132) +{ + createSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + std::vector<OUString> vTestTableNames; + + // Create an empty paragraph that will separate first table from the rest + pWrtShell->SplitNode(); + pWrtShell->StartOfSection(); + // Create a table at the start of document body + SwInsertTableOptions TableOpt(SwInsertTableFlags::DefaultBorder, 0); + const SwTable* pTable = &pWrtShell->InsertTable(TableOpt, 2, 3); + const SwTableFormat* pFormat = pTable->GetFrameFormat(); + CPPUNIT_ASSERT(pFormat); + vTestTableNames.push_back(pFormat->GetName().toString()); + pWrtShell->EndOfSection(); + // Create a table after a paragraph + pTable = &pWrtShell->InsertTable(TableOpt, 2, 3); + pFormat = pTable->GetFrameFormat(); + CPPUNIT_ASSERT(pFormat); + vTestTableNames.push_back(pFormat->GetName().toString()); + // Create a table immediately after the previous + pTable = &pWrtShell->InsertTable(TableOpt, 2, 3); + pFormat = pTable->GetFrameFormat(); + CPPUNIT_ASSERT(pFormat); + vTestTableNames.push_back(pFormat->GetName().toString()); + // Create a nested table in the middle of last row + pWrtShell->GotoTable(UIName(vTestTableNames.back())); + for (int i = 0; i < 4; ++i) + pWrtShell->GoNextCell(false); + pTable = &pWrtShell->InsertTable(TableOpt, 2, 3); + pFormat = pTable->GetFrameFormat(); + CPPUNIT_ASSERT(pFormat); + vTestTableNames.push_back(pFormat->GetName().toString()); + + // Now check that in any cell in all tables we don't go out of a cell + // using Delete or Backspace. We test cases when a table is the first node; + // when we are in a first/middle/last cell in a row; when there's a paragraph + // before/after this cell; when there's another table before/after this cell; + // in nested table. + for (const auto& rTableName : vTestTableNames) + { + pWrtShell->GotoTable(UIName(rTableName)); + do + { + const SwStartNode* pNd = pWrtShell->GetCursor()->GetPointNode().FindTableBoxStartNode(); + pWrtShell->DelRight(); + CPPUNIT_ASSERT_EQUAL(pNd, + pWrtShell->GetCursor()->GetPointNode().FindTableBoxStartNode()); + pWrtShell->DelLeft(); + CPPUNIT_ASSERT_EQUAL(pNd, + pWrtShell->GetCursor()->GetPointNode().FindTableBoxStartNode()); + } while (pWrtShell->GoNextCell(false)); + } +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testXDrawPagesSupplier) +{ + createSwDoc(); + uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent, uno::UNO_QUERY); + CPPUNIT_ASSERT_MESSAGE("XDrawPagesSupplier interface is unavailable", xDrawPagesSupplier.is()); + uno::Reference<drawing::XDrawPages> xDrawPages = xDrawPagesSupplier->getDrawPages(); + CPPUNIT_ASSERT(xDrawPages.is()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("There must be only a single DrawPage in Writer documents", + sal_Int32(1), xDrawPages->getCount()); + uno::Any aDrawPage = xDrawPages->getByIndex(0); + uno::Reference<drawing::XDrawPage> xDrawPageFromXDrawPages(aDrawPage, uno::UNO_QUERY); + CPPUNIT_ASSERT(xDrawPageFromXDrawPages.is()); + + uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage(); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + "The DrawPage accessed using XDrawPages must be the same as using XDrawPageSupplier", + xDrawPage.get(), xDrawPageFromXDrawPages.get()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf116403) +{ + createSwDoc("tdf116403-considerborders.odt"); + // Check that before ToX update, the tab stop position is the old one + uno::Reference<text::XTextRange> xParagraph = getParagraph(2, u"1\t1"_ustr); + auto aTabs = getProperty<uno::Sequence<style::TabStop>>(xParagraph, u"ParaTabStops"_ustr); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aTabs.getLength()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(17000), aTabs[0].Position); + + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + const SwTOXBase* pTOX = pWrtShell->GetTOX(0); + CPPUNIT_ASSERT(pTOX); + pWrtShell->UpdateTableOf(*pTOX); + + xParagraph = getParagraph(2, u"1\t1"_ustr); + aTabs = getProperty<uno::Sequence<style::TabStop>>(xParagraph, u"ParaTabStops"_ustr); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aTabs.getLength()); + // This was still 17000, refreshing ToX didn't take borders spacings and widths into account + CPPUNIT_ASSERT_EQUAL_MESSAGE("Page borders must be considered for right-aligned tabstop", + static_cast<sal_Int32>(17000 - 2 * 500 - 2 * 1 - 1), + aTabs[0].Position); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testHtmlCopyImages) +{ + // Load a document with an image. + createSwDoc("image.odt"); + SwDoc* pDoc = getSwDoc(); + + // Trigger the copy part of HTML copy&paste. + WriterRef xWrt = new SwHTMLWriter(/*rBaseURL=*/OUString()); + CPPUNIT_ASSERT(xWrt.is()); + + xWrt->m_bWriteClipboardDoc = true; + xWrt->m_bWriteOnlyFirstTable = false; + xWrt->SetShowProgress(false); + { + SvFileStream aStream(maTempFile.GetURL(), StreamMode::WRITE | StreamMode::TRUNC); + SwWriter aWrt(aStream, *pDoc); + aWrt.Write(xWrt); + } + htmlDocUniquePtr pHtmlDoc = parseHtml(maTempFile); + CPPUNIT_ASSERT(pHtmlDoc); + + // This failed, image was lost during HTML copy. + OUString aImage = getXPath(pHtmlDoc, "/html/body/p/img", "src"); + // Also make sure that the image is not embedded (e.g. Word doesn't handle + // embedded images). + CPPUNIT_ASSERT(aImage.startsWith("file:///")); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf158198) +{ + createSwDoc("tdf158198.odt"); + uno::Reference<text::XBookmarksSupplier> xBookmarksSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XIndexAccess> xBookmarksByIdx(xBookmarksSupplier->getBookmarks(), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), xBookmarksByIdx->getCount()); + uno::Reference<container::XNameAccess> xBookmarksByName = xBookmarksSupplier->getBookmarks(); + CPPUNIT_ASSERT(xBookmarksByName->hasByName(u"WORD"_ustr)); + CPPUNIT_ASSERT(xBookmarksByName->hasByName(u"PARAGRAPH"_ustr)); + + uno::Reference<text::XTextContent> xBookmark1(xBookmarksByName->getByName(u"WORD"_ustr), + uno::UNO_QUERY); + uno::Reference<text::XTextRange> xAnchor1 = xBookmark1->getAnchor(); + + uno::Reference<text::XTextContent> xBookmark2(xBookmarksByName->getByName(u"PARAGRAPH"_ustr), + uno::UNO_QUERY); + uno::Reference<text::XTextRange> xAnchor2 = xBookmark2->getAnchor(); + CPPUNIT_ASSERT_EQUAL(u"{WORD}"_ustr, xAnchor1->getString()); + CPPUNIT_ASSERT_EQUAL(u"{PARAGRAPH}"_ustr, xAnchor2->getString()); + + xAnchor1->setString(""); + xAnchor2->setString(""); + + // Without the fix in place, this test would have failed here + // - Expected: 2 + // - Actual : 1 + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), xBookmarksByIdx->getCount()); + CPPUNIT_ASSERT(xAnchor1->getString().isEmpty()); + CPPUNIT_ASSERT(xAnchor2->getString().isEmpty()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf116789) +{ + createSwDoc("tdf116789.fodt"); + uno::Reference<text::XBookmarksSupplier> xBookmarksSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XText> xText1; + uno::Reference<text::XText> xText2; + { + uno::Reference<text::XTextContent> xBookmark( + xBookmarksSupplier->getBookmarks()->getByName(u"Bookmark 1"_ustr), uno::UNO_QUERY); + xText1 = xBookmark->getAnchor()->getText(); + } + { + uno::Reference<text::XTextContent> xBookmark( + xBookmarksSupplier->getBookmarks()->getByName(u"Bookmark 1"_ustr), uno::UNO_QUERY); + xText2 = xBookmark->getAnchor()->getText(); + } + // This failed, we got two different SwXCell for the same bookmark anchor text. + CPPUNIT_ASSERT_EQUAL(xText1, xText2); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf91801) +{ + // Tests calculation with several user field variables without prior user fields + createSwDoc("tdf91801.fodt"); + uno::Reference<text::XTextTable> xTable(getParagraphOrTable(1), uno::UNO_QUERY); + uno::Reference<table::XCell> xCell(xTable->getCellByName(u"A1"_ustr)); + CPPUNIT_ASSERT_EQUAL(555.0, xCell->getValue()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf51223) +{ + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + sw::UndoManager& rUndoManager = pDoc->GetUndoManager(); + SwNodeOffset nIndex = pWrtShell->GetCursor()->GetPointNode().GetIndex(); + pWrtShell->Insert(u"i"_ustr); + pWrtShell->SplitNode(true); + CPPUNIT_ASSERT_EQUAL(u"I"_ustr, static_cast<SwTextNode*>(pDoc->GetNodes()[nIndex])->GetText()); + rUndoManager.Undo(); + CPPUNIT_ASSERT_EQUAL(u"i"_ustr, static_cast<SwTextNode*>(pDoc->GetNodes()[nIndex])->GetText()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testFontEmbedding) +{ +#if HAVE_MORE_FONTS && !defined(MACOSX) + createSwDoc("testFontEmbedding.odt"); + + OString aContentBaseXpath("/office:document-content/office:font-face-decls"_ostr); + OString aStylesBaseXpath("/office:document-styles/office:font-face-decls"_ostr); + OString aSettingsBaseXpath( + "/office:document-settings/office:settings/config:config-item-set"_ostr); + + xmlDocUniquePtr pXmlDoc; + + // Get document settings + uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xProps( + xFactory->createInstance(u"com.sun.star.document.Settings"_ustr), uno::UNO_QUERY_THROW); + + // Check font embedding state + CPPUNIT_ASSERT_EQUAL(false, xProps->getPropertyValue(u"EmbedFonts"_ustr).get<bool>()); + CPPUNIT_ASSERT_EQUAL(false, xProps->getPropertyValue(u"EmbedOnlyUsedFonts"_ustr).get<bool>()); + // Font scripts should be enabled by default, however this has no effect unless "EmbedOnlyUsedFonts" is enabled + CPPUNIT_ASSERT_EQUAL(true, xProps->getPropertyValue(u"EmbedLatinScriptFonts"_ustr).get<bool>()); + CPPUNIT_ASSERT_EQUAL(true, xProps->getPropertyValue(u"EmbedAsianScriptFonts"_ustr).get<bool>()); + CPPUNIT_ASSERT_EQUAL(true, + xProps->getPropertyValue(u"EmbedComplexScriptFonts"_ustr).get<bool>()); + + // CASE 1 - no font embedding enabled + + // Save the document + save(u"writer8"_ustr); + CPPUNIT_ASSERT(maTempFile.IsValid()); + + // Check setting - No font embedding should be enabled + pXmlDoc = parseExport(u"settings.xml"_ustr); + CPPUNIT_ASSERT(pXmlDoc); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedFonts']", u"false"); + + // Check styles - No font-face-src nodes should be present + pXmlDoc = parseExport(u"styles.xml"_ustr); + CPPUNIT_ASSERT(pXmlDoc); + + assertXPath(pXmlDoc, aStylesBaseXpath + "/style:font-face['CASE 1']", 6); + for (auto fontName : { "Caladea", "Carlito", "Liberation Sans", "Liberation Sans1", + "Liberation Serif", "Liberation Serif1" }) + { + OString prefix = aStylesBaseXpath + "/style:font-face[@style:name='" + fontName + "']"; + assertXPath(pXmlDoc, prefix + "['CASE 1']"); + assertXPath(pXmlDoc, prefix + "/svg:font-face-src['CASE 1']", 0); + } + + // Check content - No font-face-src nodes should be present + pXmlDoc = parseExport(u"content.xml"_ustr); + CPPUNIT_ASSERT(pXmlDoc); + + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face['CASE 1']", 6); + for (auto fontName : { "Caladea", "Carlito", "Liberation Sans", "Liberation Sans1", + "Liberation Serif", "Liberation Serif1" }) + { + OString prefix = aContentBaseXpath + "/style:font-face[@style:name='" + fontName + "']"; + assertXPath(pXmlDoc, prefix + "['CASE 1']"); + assertXPath(pXmlDoc, prefix + "/svg:font-face-src['CASE 1']", 0); + } + + // CASE 2 - font embedding enabled, but embed used fonts disabled + + // Enable font embedding, disable embedding used font only + xProps->setPropertyValue(u"EmbedFonts"_ustr, uno::Any(true)); + xProps->setPropertyValue(u"EmbedOnlyUsedFonts"_ustr, uno::Any(false)); + + // Save the document again + save(u"writer8"_ustr); + CPPUNIT_ASSERT(maTempFile.IsValid()); + + // Check setting - font embedding should be enabled + pXmlDoc = parseExport(u"settings.xml"_ustr); + CPPUNIT_ASSERT(pXmlDoc); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedFonts']", u"true"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedOnlyUsedFonts']", + u"false"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedLatinScriptFonts']", + u"true"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedAsianScriptFonts']", + u"true"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedComplexScriptFonts']", + u"true"); + + // Check styles - font-face-src should be present for all fonts + pXmlDoc = parseExport(u"styles.xml"_ustr); + CPPUNIT_ASSERT(pXmlDoc); + + assertXPath(pXmlDoc, aStylesBaseXpath + "/style:font-face['CASE 2']", 6); + for (auto fontName : { "Caladea", "Carlito", "Liberation Sans", "Liberation Sans1", + "Liberation Serif", "Liberation Serif1" }) + { + OString prefix = aStylesBaseXpath + "/style:font-face[@style:name='" + fontName + "']"; + assertXPath(pXmlDoc, prefix + "['CASE 2']"); + assertXPath(pXmlDoc, prefix + "/svg:font-face-src['CASE 2']", 1); + } + + // Check content - font-face-src should be present for all fonts + pXmlDoc = parseExport(u"content.xml"_ustr); + CPPUNIT_ASSERT(pXmlDoc); + + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face['CASE 2']", 6); + for (auto fontName : { "Caladea", "Carlito", "Liberation Sans", "Liberation Sans1", + "Liberation Serif", "Liberation Serif1" }) + { + OString prefix = aContentBaseXpath + "/style:font-face[@style:name='" + fontName + "']"; + assertXPath(pXmlDoc, prefix + "['CASE 2']"); + assertXPath(pXmlDoc, prefix + "/svg:font-face-src['CASE 2']", 1); + } + + // CASE 3 - font embedding enabled, embed only used fonts enabled + + // Enable font embedding and setting to embed used fonts only + xProps->setPropertyValue(u"EmbedFonts"_ustr, uno::Any(true)); + xProps->setPropertyValue(u"EmbedOnlyUsedFonts"_ustr, uno::Any(true)); + xProps->setPropertyValue(u"EmbedLatinScriptFonts"_ustr, uno::Any(true)); + xProps->setPropertyValue(u"EmbedAsianScriptFonts"_ustr, uno::Any(true)); + xProps->setPropertyValue(u"EmbedComplexScriptFonts"_ustr, uno::Any(true)); + + // Save the document again + save(u"writer8"_ustr); + CPPUNIT_ASSERT(maTempFile.IsValid()); + + // Check setting - font embedding should be enabled + embed only used fonts and scripts + pXmlDoc = parseExport(u"settings.xml"_ustr); + CPPUNIT_ASSERT(pXmlDoc); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedFonts']", u"true"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedOnlyUsedFonts']", + u"true"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedLatinScriptFonts']", + u"true"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedAsianScriptFonts']", + u"true"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedComplexScriptFonts']", + u"true"); + + // Check styles - font-face-src should be present only for "Liberation Serif" fonts + pXmlDoc = parseExport(u"styles.xml"_ustr); + CPPUNIT_ASSERT(pXmlDoc); + + assertXPath(pXmlDoc, aStylesBaseXpath + "/style:font-face['CASE 3']", 6); + for (auto fontName : { "Caladea", "Carlito", "Liberation Sans", "Liberation Sans1" }) + { + OString prefix = aStylesBaseXpath + "/style:font-face[@style:name='" + fontName + "']"; + assertXPath(pXmlDoc, prefix + "['CASE 3']"); + assertXPath(pXmlDoc, prefix + "/svg:font-face-src['CASE 3']", 0); + } + for (auto fontName : { "Liberation Serif", "Liberation Serif1" }) + { + OString prefix = aStylesBaseXpath + "/style:font-face[@style:name='" + fontName + "']"; + assertXPath(pXmlDoc, prefix + "['CASE 3']"); + assertXPath(pXmlDoc, prefix + "/svg:font-face-src['CASE 3']", 1); + } + + // Check content - font-face-src should be present only for Carlito and Liberation Serif fonts + // Note that the used sets of fonts are different for styles.xml and content.xml + pXmlDoc = parseExport(u"content.xml"_ustr); + CPPUNIT_ASSERT(pXmlDoc); + + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face['CASE 3']", 6); + for (auto fontName : { "Caladea", "Liberation Sans", "Liberation Sans1" }) + { + OString prefix = aContentBaseXpath + "/style:font-face[@style:name='" + fontName + "']"; + assertXPath(pXmlDoc, prefix + "['CASE 3']"); + assertXPath(pXmlDoc, prefix + "/svg:font-face-src['CASE 3']", 0); + } + for (auto fontName : { "Carlito", "Liberation Serif", "Liberation Serif1" }) + { + OString prefix = aContentBaseXpath + "/style:font-face[@style:name='" + fontName + "']"; + assertXPath(pXmlDoc, prefix + "['CASE 3']"); + assertXPath(pXmlDoc, prefix + "/svg:font-face-src['CASE 3']", 1); + } +#endif +} + +// Unit test for fix inconsistent bookmark behavior around at-char/as-char anchored frames +// +// We have a placeholder character in the sw doc model for as-char anchored frames, +// so it's possible to have a bookmark before/after the frame or a non-collapsed bookmark +// which covers the frame. The same is not true for at-char anchored frames, +// where the anchor points to a doc model position, but there is no placeholder character. +// If a bookmark is created covering the start and end of the anchor of the frame, +// internally we create a collapsed bookmark which has the same position as the anchor of the frame. +// When this doc model is handled by SwXParagraph::createEnumeration(), +// first the frame and then the bookmark is appended to the text portion enumeration, +// so your bookmark around the frame is turned into a collapsed bookmark after the frame. +// (The same happens when we roundtrip an ODT document representing this doc model.) +// +// Fix the problem by inserting collapsed bookmarks with affected anchor positions +// (same position is the anchor for an at-char frame) into the enumeration in two stages: +// first the start of them before frames and then the end of them + other bookmarks. +// This way UNO API users get their non-collapsed bookmarks around at-char anchored frames, +// similar to as-char ones. +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testInconsistentBookmark) +{ + // create test document with text and bookmark + { + createSwDoc("testInconsistentBookmark.ott"); + SwDoc* pDoc = getSwDoc(); + IDocumentMarkAccess& rIDMA(*pDoc->getIDocumentMarkAccess()); + SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1); + SwCursor aPaM(SwPosition(aIdx), nullptr); + aPaM.SetMark(); + aPaM.MovePara(GoCurrPara, fnParaStart); + aPaM.MovePara(GoCurrPara, fnParaEnd); + rIDMA.makeMark(aPaM, SwMarkName(u"Mark"_ustr), IDocumentMarkAccess::MarkType::BOOKMARK, + ::sw::mark::InsertMode::New); + aPaM.Exchange(); + aPaM.DeleteMark(); + } + + // save document and verify the bookmark scoup + { + // save document + save(u"writer8"_ustr); + + // load only content.xml + xmlDocUniquePtr pXmlDoc = parseExport(u"content.xml"_ustr); + static constexpr const char* aPath( + "/office:document-content/office:body/office:text/text:p"); + + const int pos1 = getXPathPosition(pXmlDoc, aPath, "bookmark-start"); + const int pos2 = getXPathPosition(pXmlDoc, aPath, "control"); + const int pos3 = getXPathPosition(pXmlDoc, aPath, "bookmark-end"); + + CPPUNIT_ASSERT_GREATER(pos1, pos2); + CPPUNIT_ASSERT_GREATER(pos2, pos3); + } +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testSpellOnlineParameter) +{ + createSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + const SwViewOption* pOpt = pWrtShell->GetViewOptions(); + bool bSet = pOpt->IsOnlineSpell(); + + uno::Sequence<beans::PropertyValue> params + = comphelper::InitPropertySequence({ { "Enable", uno::Any(!bSet) } }); + dispatchCommand(mxComponent, u".uno:SpellOnline"_ustr, params); + CPPUNIT_ASSERT_EQUAL(!bSet, pOpt->IsOnlineSpell()); + + // set the same state as now and we don't expect any change (no-toggle) + params = comphelper::InitPropertySequence({ { "Enable", uno::Any(!bSet) } }); + dispatchCommand(mxComponent, u".uno:SpellOnline"_ustr, params); + CPPUNIT_ASSERT_EQUAL(!bSet, pOpt->IsOnlineSpell()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf124603) +{ + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + const SwViewOption* pOpt = pWrtShell->GetViewOptions(); + uno::Sequence<beans::PropertyValue> params + = comphelper::InitPropertySequence({ { "Enable", uno::Any(true) } }); + dispatchCommand(mxComponent, u".uno:SpellOnline"_ustr, params); + + // Automatic Spell Checking is enabled + + CPPUNIT_ASSERT(pOpt->IsOnlineSpell()); + + // check available en_US dictionary and test spelling with it + uno::Reference<XLinguServiceManager2> xLngSvcMgr(GetLngSvcMgr_Impl()); + uno::Reference<XSpellChecker1> xSpell; + xSpell.set(xLngSvcMgr->getSpellChecker(), UNO_QUERY); + LanguageType eLang + = LanguageTag::convertToLanguageType(lang::Locale(u"en"_ustr, u"US"_ustr, OUString())); + if (xSpell.is() && xSpell->hasLanguage(static_cast<sal_uInt16>(eLang))) + { + // Type a correct word + + emulateTyping(u"the "); + SwCursorShell* pShell(pDoc->GetEditShell()); + CPPUNIT_ASSERT(pShell); + SwTextNode* pNode = pShell->GetCursor()->GetPointNode().GetTextNode(); + // no bad word + CPPUNIT_ASSERT_EQUAL(static_cast<SwWrongList*>(nullptr), pNode->GetWrong()); + + // Create a bad word from the good: "the" -> "thex" + + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + emulateTyping(u"x"); + // tdf#92036 pending spell checking + bool bPending = !pNode->GetWrong() || !pNode->GetWrong()->Count(); + CPPUNIT_ASSERT(bPending); + + // Move right, leave the bad word - since the fix for tdf#136294, this triggers the check + + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(pNode->GetWrong()); + // This was 0 (pending spell checking) + CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), pNode->GetWrong()->Count()); + } +} + +#if !defined(MACOSX) +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf45949) +{ + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + const SwViewOption* pOpt = pWrtShell->GetViewOptions(); + uno::Sequence<beans::PropertyValue> params + = comphelper::InitPropertySequence({ { "Enable", uno::Any(true) } }); + dispatchCommand(mxComponent, u".uno:SpellOnline"_ustr, params); + + // Automatic Spell Checking is enabled + CPPUNIT_ASSERT(pOpt->IsOnlineSpell()); + + // check available en_US dictionary and test spelling with it + uno::Reference<XLinguServiceManager2> xLngSvcMgr(GetLngSvcMgr_Impl()); + uno::Reference<XSpellChecker1> xSpell; + xSpell.set(xLngSvcMgr->getSpellChecker(), UNO_QUERY); + LanguageType eLang + = LanguageTag::convertToLanguageType(lang::Locale(u"en"_ustr, u"US"_ustr, OUString())); + if (xSpell.is() && xSpell->hasLanguage(static_cast<sal_uInt16>(eLang))) + { + emulateTyping(u"baaad http://www.baaad.org baaad"); + SwCursorShell* pShell(pDoc->GetEditShell()); + CPPUNIT_ASSERT(pShell); + SwTextNode* pNode = pShell->GetCursor()->GetPointNode().GetTextNode(); + + // tdf#152492: Without the fix in place, this test would have failed with + // - Expected: 1 + // - Actual : 3 + CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), pNode->GetWrong()->Count()); + + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 10, /*bBasicCall=*/false); + emulateTyping(u" "); + + CPPUNIT_ASSERT_EQUAL(sal_uInt16(2), pNode->GetWrong()->Count()); + + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 6, /*bBasicCall=*/false); + emulateTyping(u" "); + + // Move down to trigger spell checking + pWrtShell->Down(/*bSelect=*/false, 1); + Scheduler::ProcessEventsToIdle(); + + // Without the fix in place, this test would have failed with + // - Expected: 3 + // - Actual : 2 + CPPUNIT_ASSERT_EQUAL(sal_uInt16(3), pNode->GetWrong()->Count()); + } +} +#endif + +#if !defined(MACOSX) +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf157442) +{ + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + const SwViewOption* pOpt = pWrtShell->GetViewOptions(); + uno::Sequence<beans::PropertyValue> params + = comphelper::InitPropertySequence({ { "Enable", uno::Any(true) } }); + dispatchCommand(mxComponent, u".uno:SpellOnline"_ustr, params); + + // Automatic Spell Checking is enabled + CPPUNIT_ASSERT(pOpt->IsOnlineSpell()); + + // check available en_US dictionary and test spelling with it + uno::Reference<XLinguServiceManager2> xLngSvcMgr(GetLngSvcMgr_Impl()); + uno::Reference<XSpellChecker1> xSpell; + xSpell.set(xLngSvcMgr->getSpellChecker(), UNO_QUERY); + LanguageType eLang + = LanguageTag::convertToLanguageType(lang::Locale(u"en"_ustr, u"US"_ustr, OUString())); + if (xSpell.is() && xSpell->hasLanguage(static_cast<sal_uInt16>(eLang))) + { + uno::Reference<linguistic2::XLinguProperties> xLinguProperties( + LinguMgr::GetLinguPropertySet()); + + // Spell with digits is disabled by default + CPPUNIT_ASSERT_EQUAL(sal_False, xLinguProperties->getIsSpellWithDigits()); + + emulateTyping(u"ErrorError Treee2 "); + SwCursorShell* pShell(pDoc->GetEditShell()); + CPPUNIT_ASSERT(pShell); + SwTextNode* pNode = pShell->GetCursor()->GetPointNode().GetTextNode(); + + // Without the fix in place, this test would have crashed because GetWrong() returns nullptr + CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), pNode->GetWrong()->Count()); + } +} +#endif + +#if !defined(MACOSX) +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf65535) +{ + createSwDoc("tdf65535.fodt"); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + const SwViewOption* pOpt = pWrtShell->GetViewOptions(); + uno::Sequence<beans::PropertyValue> params + = comphelper::InitPropertySequence({ { "Enable", uno::Any(true) } }); + dispatchCommand(mxComponent, u".uno:SpellOnline"_ustr, params); + + // Automatic Spell Checking is enabled + + CPPUNIT_ASSERT(pOpt->IsOnlineSpell()); + + // check available en_US dictionary and test spelling with it + uno::Reference<XLinguServiceManager2> xLngSvcMgr(GetLngSvcMgr_Impl()); + uno::Reference<XSpellChecker1> xSpell; + xSpell.set(xLngSvcMgr->getSpellChecker(), UNO_QUERY); + LanguageType eLang + = LanguageTag::convertToLanguageType(lang::Locale(u"en"_ustr, u"US"_ustr, OUString())); + if (xSpell.is() && xSpell->hasLanguage(static_cast<sal_uInt16>(eLang))) + { + // trigger online spell checking by (a few) spaces to be sure to get it + + emulateTyping(u" "); + + // FIXME: inserting a space before the bad word removes the red underline + // Insert a second space to get the red underline (back) + + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + emulateTyping(u" "); + + // Select the bad word (right to left, as during right click) + + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 5, /*bBasicCall=*/false); + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 4, /*bBasicCall=*/false); + + // choose the word "Baaed" from the spelling suggestions of the context menu + + SfxViewShell* pViewShell = SfxViewShell::Current(); + CPPUNIT_ASSERT(pViewShell); + { + static constexpr OUStringLiteral sApplyRule(u"Spelling_Baaed"); + SfxStringItem aApplyItem(FN_PARAM_1, sApplyRule); + pViewShell->GetViewFrame().GetDispatcher()->ExecuteList( + SID_SPELLCHECK_APPLY_SUGGESTION, SfxCallMode::SYNCHRON, { &aApplyItem }); + } + + // check the replacement in the text + + CPPUNIT_ASSERT_EQUAL(u" Baaed"_ustr, getParagraph(1)->getString()); + } + + // check the remaining comment + + tools::JsonWriter aJsonWriter; + SwXTextDocument* pTextDoc = getSwTextDoc(); + pTextDoc->getPostIts(aJsonWriter); + OString pChar = aJsonWriter.finishAndGetAsOString(); + std::stringstream aStream((std::string(pChar))); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + OString sCommentText; + for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("comments")) + { + const boost::property_tree::ptree& rComment = rValue.second; + sCommentText = OString(rComment.get<std::string>("html")); + } + // This was false (lost comment with spelling replacement) + CPPUNIT_ASSERT_EQUAL("<div>with comment</div>"_ostr, sCommentText); +} +#endif + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testRedlineAutoCorrect) +{ + createSwDoc("redline-autocorrect.fodt"); + SwDoc* pDoc = getSwDoc(); + + dispatchCommand(mxComponent, u".uno:GoToEndOfDoc"_ustr, {}); + + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + // show tracked deletion with enabled change tracking + RedlineFlags const nMode(pWrtShell->GetRedlineFlags() | RedlineFlags::On); + CPPUNIT_ASSERT(nMode & (RedlineFlags::ShowDelete | RedlineFlags::ShowInsert)); + pWrtShell->SetRedlineFlags(nMode); + CPPUNIT_ASSERT(nMode & RedlineFlags::ShowDelete); + + CPPUNIT_ASSERT_MESSAGE("redlining should be on", + pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + + emulateTyping(u" "); + + // tdf#83419 This was "Ts " removing the deletion of "t" silently by sentence capitalization + OUString sReplaced(u"ts "_ustr); + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // hide delete redlines + pWrtShell->SetRedlineFlags(nMode & ~RedlineFlags::ShowDelete); + + // repeat it with not visible redlining + dispatchCommand(mxComponent, u".uno:Undo"_ustr, {}); + + emulateTyping(u" "); + + sReplaced = "S "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // show delete redlines + pWrtShell->SetRedlineFlags(nMode); + + // This still keep the tracked deletion, capitalize only the visible text "s" + // with tracked deletion of the original character + sReplaced = "tsS "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // repeat it with visible redlining and word auto replacement of "tset" + dispatchCommand(mxComponent, u".uno:Undo"_ustr, {}); + dispatchCommand(mxComponent, u".uno:Undo"_ustr, {}); + + emulateTyping(u"et "); + // This was "Ttest" removing the tracked deletion silently. + // Don't replace, if a redline starts or ends within the text. + sReplaced = "tset "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // Otherwise replace it + emulateTyping(u"tset "); + sReplaced = "tset test "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // Including capitalization + emulateTyping(u"end. word "); + sReplaced = "tset test end. Word "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // tracked deletions after the correction point doesn't affect autocorrect + dispatchCommand(mxComponent, u".uno:GoToStartOfDoc"_ustr, {}); + emulateTyping(u"a "); + sReplaced = "A tset test end. Word "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testRedlineAutoCorrect2) +{ + createSwDoc("redline-autocorrect2.fodt"); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + dispatchCommand(mxComponent, u".uno:GoToEndOfDoc"_ustr, {}); + + // show tracked deletion + RedlineFlags const nMode(pWrtShell->GetRedlineFlags() | RedlineFlags::On); + CPPUNIT_ASSERT(nMode & (RedlineFlags::ShowDelete | RedlineFlags::ShowInsert)); + pWrtShell->SetRedlineFlags(nMode); + CPPUNIT_ASSERT(nMode & RedlineFlags::ShowDelete); + + emulateTyping(u"... "); + + // This was "LoremLorem,…," (duplicating the deleted comma, but without deletion) + // Don't replace, if a redline starts or ends within the text. + OUString sReplaced = u"Lorem,... "_ustr; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // Continue it: + emulateTyping(u"Lorem,... "); + sReplaced = u"Lorem,... Lorem,… "_ustr; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testEmojiAutoCorrect) +{ + createSwDoc("redline-autocorrect3.fodt"); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + // Emoji replacement (:snowman: -> ☃) + + // without change tracking + CPPUNIT_ASSERT(!(pWrtShell->GetRedlineFlags() & RedlineFlags::On)); + emulateTyping(u":snowman:"); + OUString sReplaced = u"☃Lorem,"_ustr; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // with change tracking (showing redlines) + RedlineFlags const nMode(pWrtShell->GetRedlineFlags() | RedlineFlags::On); + CPPUNIT_ASSERT(nMode & (RedlineFlags::ShowDelete | RedlineFlags::ShowInsert)); + pWrtShell->SetRedlineFlags(nMode); + CPPUNIT_ASSERT(nMode & RedlineFlags::On); + CPPUNIT_ASSERT(nMode & RedlineFlags::ShowDelete); + + emulateTyping(u":snowman:"); + sReplaced = u"☃☃Lorem,"_ustr; + + // tdf#140674 This was ":snowman:" instead of autocorrect + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf153423) +{ + createSwDoc(); + SvxSwAutoFormatFlags flags(*SwEditShell::GetAutoFormatFlags()); + comphelper::ScopeGuard const g([=]() { SwEditShell::SetAutoFormatFlags(&flags); }); + flags.bSetNumRule = true; + SwEditShell::SetAutoFormatFlags(&flags); + + emulateTyping(u"1. Item 1"); + + SwXTextDocument* pTextDoc = getSwTextDoc(); + pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); + pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RETURN); + Scheduler::ProcessEventsToIdle(); + + // Without the fix in place, this test would have failed with + // - Expected: 1. + // - Actual : 10. + CPPUNIT_ASSERT_EQUAL(u"1."_ustr, + getProperty<OUString>(getParagraph(1), u"ListLabelString"_ustr)); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf133589) +{ + // Hungarian test document with right-to-left paragraph setting + createSwDoc("tdf133589.fodt"); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + // translitere words to Old Hungarian + emulateTyping(u"székely "); + OUString sReplaced(u"𐳥𐳋𐳓𐳉𐳗 "_ustr); + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // disambiguate consonants: asszony -> asz|szony + emulateTyping(u"asszony "); + sReplaced += u"𐳀𐳥𐳥𐳛𐳚 "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // disambiguate consonants: kosszarv -> kos|szarv + // (add explicit ZWSP temporarily for consonant disambiguation, because the requested + // hu_HU hyphenation dictionary isn't installed on all testing platform) + // pWrtShell->Insert(u"kosszarv"); + emulateTyping(u"kos\u200Bszarv "); + sReplaced += u"𐳓𐳛𐳤𐳥𐳀𐳢𐳮 "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // transliterate numbers to Old Hungarian + emulateTyping(u"2020 "); + sReplaced += u"𐳺𐳺𐳿𐳼𐳼 "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // tdf#147546 transliterate punctuation marks + + // question mark + emulateTyping(u"Kérdőjel?"); + sReplaced += u"𐲓𐳋𐳢𐳇𐳟𐳒𐳉𐳖"; + OUString sReplaced2(sReplaced + "?"); + CPPUNIT_ASSERT_EQUAL(sReplaced2, getParagraph(1)->getString()); + emulateTyping(u" "); + sReplaced += u"⸮ "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // comma + emulateTyping(u"Vessző,"); + sReplaced += u"𐲮𐳉𐳥𐳥𐳟"; + sReplaced2 = sReplaced + ","; + CPPUNIT_ASSERT_EQUAL(sReplaced2, getParagraph(1)->getString()); + emulateTyping(u" "); + sReplaced += u"⹁ "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // semicolon + emulateTyping(u"pontosvessző;"); + sReplaced += u"𐳠𐳛𐳙𐳦𐳛𐳤𐳮𐳉𐳥𐳥𐳟"; + sReplaced2 = sReplaced + ";"; + CPPUNIT_ASSERT_EQUAL(sReplaced2, getParagraph(1)->getString()); + emulateTyping(u" "); + sReplaced += u"⁏ "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // quotation marks + emulateTyping(u"„idézőjel” "); + sReplaced += u"⹂𐳐𐳇𐳋𐳯𐳟𐳒𐳉𐳖‟ "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // tdf#148672 transliterate word with closing bracket + emulateTyping(u"word] "); + sReplaced += u"𐳮𐳛𐳢𐳇] "; // This was "word]" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // tdf#148672 transliterate words with parenthesis (libnumbertext 1.0.11) + emulateTyping(u"(word) "); + sReplaced += u"(𐳮𐳛𐳢𐳇) "; // This was "(word)" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + emulateTyping(u"(word "); + sReplaced += u"(𐳮𐳛𐳢𐳇 "; // This was "(word" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + emulateTyping(u"word) "); + sReplaced += u"𐳮𐳛𐳢𐳇) "; // This was "word)" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + emulateTyping(u"{word} "); + sReplaced += u"{𐳮𐳛𐳢𐳇} "; // This was "(word)" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + emulateTyping(u"{word "); + sReplaced += u"{𐳮𐳛𐳢𐳇 "; // This was "(word" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + emulateTyping(u"word} "); + sReplaced += u"𐳮𐳛𐳢𐳇} "; // This was "word)" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + emulateTyping(u"[word] "); + sReplaced += u"[𐳮𐳛𐳢𐳇] "; // This was "(word)" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + emulateTyping(u"[word "); + sReplaced += u"[𐳮𐳛𐳢𐳇 "; // This was "(word" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testAutoCorr) +{ + createSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + //Normal AutoCorrect + emulateTyping(u"tset "); + CPPUNIT_ASSERT_EQUAL(u"Test "_ustr, getParagraph(1)->getString()); + + //AutoCorrect with change style to bolt + emulateTyping(u"Bolt "); + const uno::Reference<text::XTextRange> xRun = getRun(getParagraph(1), 2); + CPPUNIT_ASSERT_EQUAL(u"Bolt"_ustr, xRun->getString()); + CPPUNIT_ASSERT_EQUAL(u"Arial"_ustr, getProperty<OUString>(xRun, u"CharFontName"_ustr)); + + //AutoCorrect inserts Table with 2 rows and 3 columns + emulateTyping(u"4xx "); + const uno::Reference<text::XTextTable> xTable(getParagraphOrTable(2), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable->getColumns()->getCount()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf130274) +{ + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* const pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + CPPUNIT_ASSERT(!pWrtShell->GetLayout()->IsHideRedlines()); + CPPUNIT_ASSERT( + !IDocumentRedlineAccess::IsRedlineOn(pDoc->getIDocumentRedlineAccess().GetRedlineFlags())); + + // "tset" may be replaced by the AutoCorrect in the test profile + emulateTyping(u"tset"); + // select from left to right + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, /*bBasicCall=*/false); + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 4, /*bBasicCall=*/false); + + pWrtShell->SetRedlineFlags(pWrtShell->GetRedlineFlags() | RedlineFlags::On); + // this would crash in AutoCorrect + emulateTyping(u"."); + + CPPUNIT_ASSERT(!pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf83260) +{ + createSwDoc("tdf83260-1.odt"); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + // enabled but not shown + CPPUNIT_ASSERT(pWrtShell->GetLayout()->IsHideRedlines()); +#if 0 + CPPUNIT_ASSERT(IDocumentRedlineAccess::IsHideChanges( + pDoc->getIDocumentRedlineAccess().GetRedlineFlags())); +#endif + CPPUNIT_ASSERT( + IDocumentRedlineAccess::IsRedlineOn(pDoc->getIDocumentRedlineAccess().GetRedlineFlags())); + CPPUNIT_ASSERT(!pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty()); + + // the document contains redlines that are combined with CompressRedlines() + // if that happens during AutoCorrect then indexes in Undo are off -> crash + emulateTyping(u"tset "); + sw::UndoManager& rUndoManager = pDoc->GetUndoManager(); + auto const nActions(rUndoManager.GetUndoActionCount()); + for (auto i = nActions; 0 < i; --i) + { + rUndoManager.Undo(); + } + // check that every text node has a layout frame + for (SwNodeOffset i(0); i < pDoc->GetNodes().Count(); ++i) + { + if (SwTextNode const* const pNode = pDoc->GetNodes()[i]->GetTextNode()) + { + CPPUNIT_ASSERT(pNode->getLayoutFrame(nullptr, nullptr, nullptr)); + } + } + for (auto i = nActions; 0 < i; --i) + { + rUndoManager.Redo(); + } + for (SwNodeOffset i(0); i < pDoc->GetNodes().Count(); ++i) + { + if (SwTextNode const* const pNode = pDoc->GetNodes()[i]->GetTextNode()) + { + CPPUNIT_ASSERT(pNode->getLayoutFrame(nullptr, nullptr, nullptr)); + } + } + for (auto i = nActions; 0 < i; --i) + { + rUndoManager.Undo(); + } + for (SwNodeOffset i(0); i < pDoc->GetNodes().Count(); ++i) + { + if (SwTextNode const* const pNode = pDoc->GetNodes()[i]->GetTextNode()) + { + CPPUNIT_ASSERT(pNode->getLayoutFrame(nullptr, nullptr, nullptr)); + } + } +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf139922) +{ + createSwDoc(); + + SwXTextDocument* pTextDoc = getSwTextDoc(); + pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); + Scheduler::ProcessEventsToIdle(); + + emulateTyping(u"this is a SEntence. this is a SEntence."); + + // Without the fix in place, this test would have failed with + // - Expected: This is a Sentence. This is a Sentence. + // - Actual : this is a Sentence. This is a Sentence. + CPPUNIT_ASSERT_EQUAL(u"This is a Sentence. This is a Sentence."_ustr, + getParagraph(2)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf143176) +{ + // Hungarian test document with right-to-left paragraph setting + createSwDoc("tdf143176.fodt"); + + // transliterate the document to Old Hungarian (note: it only works + // with right-to-left text direction and Default Paragraph Style) + dispatchCommand(mxComponent, u".uno:AutoFormatApply"_ustr, {}); + + // This was the original "Lorem ipsum..." + CPPUNIT_ASSERT_EQUAL(u"𐲖𐳛𐳢𐳉𐳘 𐳐𐳠𐳤𐳪𐳘 𐳇𐳛𐳖𐳛𐳢 " + u"𐳤𐳐𐳦 𐳀𐳘𐳉𐳦⹁"_ustr, + getParagraph(1)->getString()); + CPPUNIT_ASSERT_EQUAL(u"𐳄𐳛𐳙𐳤𐳉𐳄𐳦𐳉𐳦𐳪𐳢 " + u"𐳀𐳇𐳐𐳠𐳐𐳤𐳄𐳐𐳙𐳍 𐳉𐳖𐳐𐳦."_ustr, + getParagraph(2)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testInsertLongDateFormat) +{ + // only for Hungarian, yet + createSwDoc("tdf133524.fodt"); + dispatchCommand(mxComponent, u".uno:InsertDateField"_ustr, {}); + // Make sure that the document starts with a field now, and its expanded string value contains space + const uno::Reference<text::XTextRange> xField = getRun(getParagraph(1), 1); + CPPUNIT_ASSERT_EQUAL(u"TextField"_ustr, getProperty<OUString>(xField, u"TextPortionType"_ustr)); + // the date format was "YYYY-MM-DD", but now "YYYY. MMM DD." + CPPUNIT_ASSERT(xField->getString().indexOf(" ") > -1); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf129270) +{ + createSwDoc("tdf129270.odt"); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + // Go to document end + pWrtShell->SttEndDoc(/*bStt=*/false); + + // Press enter + SwXTextDocument* pTextDoc = getSwTextDoc(); + pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); + Scheduler::ProcessEventsToIdle(); + + // Numbering for previous outline should remain the same "2" + CPPUNIT_ASSERT_EQUAL(u"2"_ustr, + getProperty<OUString>(getParagraph(4), u"ListLabelString"_ustr)); + + // Numbering for newly created outline should be "2.1" + CPPUNIT_ASSERT_EQUAL(u"2.1"_ustr, + getProperty<OUString>(getParagraph(5), u"ListLabelString"_ustr)); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testInsertPdf) +{ + auto pPdfium = vcl::pdf::PDFiumLibrary::get(); + if (!pPdfium) + { + return; + } + + createSwDoc(); + + // insert the PDF into the document + uno::Sequence<beans::PropertyValue> aArgs(comphelper::InitPropertySequence( + { { "FileName", uno::Any(createFileURL(u"hello-world.pdf")) } })); + dispatchCommand(mxComponent, u".uno:InsertGraphic"_ustr, aArgs); + + // Save and load cycle + saveAndReload(u"writer8"_ustr); + + uno::Reference<drawing::XShape> xShape = getShape(1); + // Assert that we have a replacement graphics + auto xReplacementGraphic + = getProperty<uno::Reference<graphic::XGraphic>>(xShape, u"ReplacementGraphic"_ustr); + CPPUNIT_ASSERT(xReplacementGraphic.is()); + + auto xGraphic = getProperty<uno::Reference<graphic::XGraphic>>(xShape, u"Graphic"_ustr); + CPPUNIT_ASSERT(xGraphic.is()); + // Assert that the graphic is a PDF + CPPUNIT_ASSERT_EQUAL(u"application/pdf"_ustr, + getProperty<OUString>(xGraphic, u"MimeType"_ustr)); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf143760WrapContourToOff) +{ + // Actually, this is an ooxmlexport test. It is here because here is a ready environment + // to change a shape by dispatchCommand. + createSwDoc("tdf143760_ContourToWrapOff.docx"); + SwDoc* pDoc = getSwDoc(); + CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(1), u"SurroundContour"_ustr)); + + // Mark the object + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + SdrPage* pPage = pDoc->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + CPPUNIT_ASSERT(pObject); + SdrView* pView = pWrtShell->GetDrawView(); + pView->MarkObj(pObject, pView->GetSdrPageView()); + + // Set "wrap off" + dispatchCommand(mxComponent, u".uno:WrapOff"_ustr, {}); + CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(getShape(1), u"SurroundContour"_ustr)); + + // Without fix this had failed, because the shape was written to file with contour. + saveAndReload(u"Office Open XML Text"_ustr); + CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(getShape(1), u"SurroundContour"_ustr)); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testHatchFill) +{ + createSwDoc(); + + // Add a rectangle shape to the document. + uno::Reference<css::lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY); + uno::Reference<drawing::XShape> xShape( + xFactory->createInstance(u"com.sun.star.drawing.RectangleShape"_ustr), uno::UNO_QUERY); + xShape->setSize(awt::Size(10000, 10000)); + xShape->setPosition(awt::Point(1000, 1000)); + uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY); + xShapeProps->setPropertyValue(u"FillStyle"_ustr, uno::Any(drawing::FillStyle_HATCH)); + xShapeProps->setPropertyValue(u"FillHatchName"_ustr, uno::Any(u"Black 0 Degrees"_ustr)); + xShapeProps->setPropertyValue(u"FillBackground"_ustr, uno::Any(false)); + xShapeProps->setPropertyValue(u"FillTransparence"_ustr, uno::Any(sal_Int32(30))); + uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage(); + xDrawPage->add(xShape); + + // Save it as DOCX and load it again. + saveAndReload(u"Office Open XML Text"_ustr); + CPPUNIT_ASSERT_EQUAL(1, getShapes()); + + // tdf#127989 Without fix this had failed, because the background of the hatch was not set as 'no background'. + CPPUNIT_ASSERT(!getProperty<bool>(getShape(1), u"FillBackground"_ustr)); + + // tdf#146822 Without fix this had failed, because the transparency value of the hatch was not exported. + CPPUNIT_ASSERT_EQUAL(sal_Int32(30), + getProperty<sal_Int32>(getShape(1), u"FillTransparence"_ustr)); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testNestedGroupTextBoxCopyCrash) +{ + createSwDoc("tdf149550.docx"); + + CPPUNIT_ASSERT_EQUAL(1, getShapes()); + + dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {}); + dispatchCommand(mxComponent, u".uno:Copy"_ustr, {}); + // This crashed here before the fix. + SwXTextDocument* pTextDoc = getSwTextDoc(); + pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_ESCAPE); + Scheduler::ProcessEventsToIdle(); + dispatchCommand(mxComponent, u".uno:Paste"_ustr, {}); + + CPPUNIT_ASSERT_EQUAL(2, getShapes()); + + auto pLayout = parseLayoutDump(); + // There must be 2 textboxes! + assertXPath(pLayout, "/root/page/body/txt/anchored/fly[2]"); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testCrashOnExit) +{ + // Load the bugdoc with a table and a textbox shape inside. + createSwDoc("tdf142715.odt"); + + // Get the textbox selected + CPPUNIT_ASSERT_EQUAL(1, getShapes()); + + selectShape(1); + auto xShape = getShape(1); + uno::Reference<beans::XPropertySet> xProperties(xShape, uno::UNO_QUERY); + + // Check if the textbox is selected + CPPUNIT_ASSERT_EQUAL(true, xProperties->getPropertyValue(u"TextBox"_ustr).get<bool>()); + + // Remove the textbox + dispatchCommand(mxComponent, u".uno:RemoveTextBox"_ustr, {}); + + CPPUNIT_ASSERT_EQUAL(false, xProperties->getPropertyValue(u"TextBox"_ustr).get<bool>()); + + // Readd the textbox (to run the textboxhelper::create() method) + dispatchCommand(mxComponent, u".uno:AddTextBox"_ustr, {}); + + CPPUNIT_ASSERT_EQUAL(true, xProperties->getPropertyValue(u"TextBox"_ustr).get<bool>()); + + // save and reload + // Before the fix this crashed here and could not reopen. + saveAndReload(u"writer8"_ustr); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testCaptionShape) +{ + createSwDoc(); + + // Add a caption shape to the document. + uno::Reference<css::lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY); + uno::Reference<drawing::XShape> xShape( + xFactory->createInstance(u"com.sun.star.drawing.CaptionShape"_ustr), uno::UNO_QUERY); + xShape->setSize(awt::Size(10000, 10000)); + xShape->setPosition(awt::Point(1000, 1000)); + uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage(); + xDrawPage->add(xShape); + + // Save it as DOCX and load it again. + saveAndReload(u"Office Open XML Text"_ustr); + + // Without fix in place, the shape was lost on export. + CPPUNIT_ASSERT_EQUAL(1, getShapes()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf151828_Comment2) +{ + createSwDoc(); + + // Add a basic shape to the document. + uno::Sequence<beans::PropertyValue> aArgs( + comphelper::InitPropertySequence({ { "KeyModifier", uno::Any(KEY_MOD1) } })); + dispatchCommand(mxComponent, u".uno:BasicShapes"_ustr, aArgs); + + auto xBasicShape = getShape(1); + auto pObject = SdrObject::getSdrObjectFromXShape(xBasicShape); + + CPPUNIT_ASSERT_EQUAL(1, getShapes()); + + // rename the shape name + pObject->SetName(u"Shape"_ustr); + + // cut and paste it + dispatchCommand(mxComponent, u".uno:Cut"_ustr, {}); + + CPPUNIT_ASSERT_EQUAL(0, getShapes()); + + dispatchCommand(mxComponent, u".uno:Paste"_ustr, {}); + + CPPUNIT_ASSERT_EQUAL(1, getShapes()); + + // it is required to get the shape object again after paste + xBasicShape = getShape(1); + pObject = SdrObject::getSdrObjectFromXShape(xBasicShape); + + // Without fix in place, the shape name was 'Shape 1' after paste. + CPPUNIT_ASSERT_EQUAL(u"Shape"_ustr, pObject->GetName()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf151828) +{ + createSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + + // insert a table + SwInsertTableOptions TableOpt(SwInsertTableFlags::DefaultBorder, 0); + pWrtShell->InsertTable(TableOpt, 1, 1); + + // move cursor into the table + CPPUNIT_ASSERT(pWrtShell->MoveTable(GotoPrevTable, fnTableStart)); + + SwFrameFormat* pFormat = pWrtShell->GetTableFormat(); + CPPUNIT_ASSERT(pFormat); + + // set name of table to 'MyTableName' + pWrtShell->SetTableName(*pFormat, UIName(u"MyTableName"_ustr)); + + // cut and paste the table + dispatchCommand(mxComponent, u".uno:SelectTable"_ustr, {}); + dispatchCommand(mxComponent, u".uno:Cut"_ustr, {}); + dispatchCommand(mxComponent, u".uno:Paste"_ustr, {}); + + // move cursor into the pasted table + CPPUNIT_ASSERT(pWrtShell->MoveTable(GotoPrevTable, fnTableStart)); + + pFormat = pWrtShell->GetTableFormat(); + CPPUNIT_ASSERT(pFormat); + + // Before the fix the pasted table name was 'Table1'. + CPPUNIT_ASSERT_EQUAL(u"MyTableName"_ustr, pFormat->GetName().toString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf146178) +{ + createSwDoc(); + + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + SwCursorShell* pShell(pDoc->GetEditShell()); + CPPUNIT_ASSERT(pShell); + SwPaM* pCursor = pShell->GetCursor(); + + // insert two fields + dispatchCommand(mxComponent, u".uno:InsertTimeField"_ustr, {}); + dispatchCommand(mxComponent, u".uno:InsertDateField"_ustr, {}); + + // navigate by field + SwView::SetMoveType(NID_FIELD); + + // set cursor to the start of the document + pWrtShell->SttEndDoc(false); + // navigate to the previous field + dispatchCommand(mxComponent, u".uno:ScrollToPrevious"_ustr, {}); + // Before the fix the position would be 0, navigation did not wrap to end of document + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pCursor->GetPoint()->GetContentIndex()); + + // set cursor to the end of the document + pWrtShell->SttEndDoc(false); + // navigate to the next field + dispatchCommand(mxComponent, u".uno:ScrollToNext"_ustr, {}); + // Before the fix the position would be 1, navigation did not wrap to start of document + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), pCursor->GetPoint()->GetContentIndex()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf106663HeaderTextFrameGoToNextPlacemarker) +{ + createSwDoc("testTdf106663.odt"); + + SwDoc* pDoc = getSwDoc(); + SwCursorShell* pShell(pDoc->GetEditShell()); + CPPUNIT_ASSERT(pShell); + SwPaM* pCursor = pShell->GetCursor(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + + // Move the cursor into the fly frame of the document's header + pWrtShell->GotoFly(UIName(u"FrameInHeader"_ustr), FLYCNTTYPE_FRM, false); + + // Check that GoToNextPlacemarker highlights the first field instead of the second one + dispatchCommand(mxComponent, u".uno:GoToNextPlacemarker"_ustr, {}); + // Without the fix in place, this test would have failed with + // - Expected: Heading + // - Actual : Some other marker + // i.e. the GoToNextPlacemarker command skipped the first field + CPPUNIT_ASSERT(pCursor->GetPoint()->GetNode().GetTextNode()->GetText().startsWith("Heading")); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf158454) +{ + createSwDoc("tdf158454.odt"); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + // * without change tracking + CPPUNIT_ASSERT(!(pWrtShell->GetRedlineFlags() & RedlineFlags::On)); + + // Thai single autocorrect (อนุญาติ -> อนุญาต) + emulateTyping(u"อนุญาติ "); + OUString sReplaced = u"อนุญาต (จบ)"_ustr; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // Thai multiple autocorrects (กงศุลสังเกตุกระทันหัน -> กงสุลสังเกตกะทันหัน) + emulateTyping(u"กงศุลสังเกตุกระทันหัน "); + sReplaced = u"อนุญาต กงสุลสังเกตกะทันหัน (จบ)"_ustr; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // * with change tracking (showing redlines) + RedlineFlags const nMode(pWrtShell->GetRedlineFlags() | RedlineFlags::On); + CPPUNIT_ASSERT(nMode & (RedlineFlags::ShowDelete | RedlineFlags::ShowInsert)); + pWrtShell->SetRedlineFlags(nMode); + CPPUNIT_ASSERT(nMode & RedlineFlags::On); + CPPUNIT_ASSERT(nMode & RedlineFlags::ShowDelete); + + // Thai single autocorrect (อนุญาติ -> อนุญาต) + emulateTyping(u"อนุญาติ "); + sReplaced = u"อนุญาต กงสุลสังเกตกะทันหัน อนุญาต (จบ)"_ustr; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // Thai multiple autocorrects (กงศุลสังเกตุกระทันหัน -> กงสุลสังเกตกะทันหัน) + emulateTyping(u"กงศุลสังเกตุกระทันหัน "); + sReplaced = u"อนุญาต กงสุลสังเกตกะทันหัน อนุญาต กงสุลสังเกตกะทันหัน (จบ)"_ustr; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |