diff options
Diffstat (limited to 'sw/qa/core/txtnode')
-rw-r--r-- | sw/qa/core/txtnode/data/btlr-cell-chinese.doc | bin | 0 -> 38912 bytes | |||
-rw-r--r-- | sw/qa/core/txtnode/data/fly-anchor-undo.odt | bin | 0 -> 10299 bytes | |||
-rw-r--r-- | sw/qa/core/txtnode/data/textbox-copy-anchor.docx | bin | 0 -> 224854 bytes | |||
-rw-r--r-- | sw/qa/core/txtnode/data/textbox-node-split.docx | bin | 0 -> 20929 bytes | |||
-rw-r--r-- | sw/qa/core/txtnode/data/title-field-invalidate.fodt | 25 | ||||
-rw-r--r-- | sw/qa/core/txtnode/justify.cxx | 182 | ||||
-rw-r--r-- | sw/qa/core/txtnode/txtnode.cxx | 323 |
7 files changed, 530 insertions, 0 deletions
diff --git a/sw/qa/core/txtnode/data/btlr-cell-chinese.doc b/sw/qa/core/txtnode/data/btlr-cell-chinese.doc Binary files differnew file mode 100644 index 000000000..a4a70f677 --- /dev/null +++ b/sw/qa/core/txtnode/data/btlr-cell-chinese.doc diff --git a/sw/qa/core/txtnode/data/fly-anchor-undo.odt b/sw/qa/core/txtnode/data/fly-anchor-undo.odt Binary files differnew file mode 100644 index 000000000..dd2093161 --- /dev/null +++ b/sw/qa/core/txtnode/data/fly-anchor-undo.odt diff --git a/sw/qa/core/txtnode/data/textbox-copy-anchor.docx b/sw/qa/core/txtnode/data/textbox-copy-anchor.docx Binary files differnew file mode 100644 index 000000000..b835097f1 --- /dev/null +++ b/sw/qa/core/txtnode/data/textbox-copy-anchor.docx diff --git a/sw/qa/core/txtnode/data/textbox-node-split.docx b/sw/qa/core/txtnode/data/textbox-node-split.docx Binary files differnew file mode 100644 index 000000000..5760ee6e2 --- /dev/null +++ b/sw/qa/core/txtnode/data/textbox-node-split.docx diff --git a/sw/qa/core/txtnode/data/title-field-invalidate.fodt b/sw/qa/core/txtnode/data/title-field-invalidate.fodt new file mode 100644 index 000000000..e7d07cab7 --- /dev/null +++ b/sw/qa/core/txtnode/data/title-field-invalidate.fodt @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<office:document xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:meta> + <dc:subject>mysubject</dc:subject> + <dc:title>mytitle</dc:title> + <meta:user-defined meta:name="Version" meta:value-type="string">1.1</meta:user-defined> + </office:meta> + <office:automatic-styles> + <style:page-layout style:name="pm1"> + <style:page-layout-properties fo:page-width="14.8cm" fo:page-height="21.0cm" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm"/> + </style:page-layout> + </office:automatic-styles> + <office:master-styles> + <style:master-page style:name="Standard" style:display-name="Standard" style:page-layout-name="pm1" style:next-style-name="Standard"> + <style:footer> + <text:p><text:subject>mysubject</text:subject> <text:title>mytitle</text:title> <text:user-defined text:name="Version">1.1</text:user-defined> <text:modification-date>May 18, 2021</text:modification-date></text:p> + </style:footer> + </style:master-page> + </office:master-styles> + <office:body> + <office:text> + <text:p>body text</text:p> + </office:text> + </office:body> +</office:document> diff --git a/sw/qa/core/txtnode/justify.cxx b/sw/qa/core/txtnode/justify.cxx new file mode 100644 index 000000000..0bdcd01e3 --- /dev/null +++ b/sw/qa/core/txtnode/justify.cxx @@ -0,0 +1,182 @@ +/* -*- 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 <swmodeltestbase.hxx> +#include <justify.hxx> +#include <cstddef> +#include <vector> + +namespace +{ +/// Covers sw/source/core/txtnode/justify fixes. +class SwCoreJustifyTest : public SwModelTestBase +{ +}; + +/// CharWidthArray: store char widths because they are more readable. +class CharWidthArray +{ +public: + std::vector<sal_Int32> maArray; + template <typename... Args> + CharWidthArray(Args&&... args) + : maArray{ std::forward<Args>(args)... } + { + } + template <typename Function> void InvokeWithKernArray(Function f); + void ConvertToKernArray(); + void ConvertToCharWidths(); +}; + +inline bool operator==(const CharWidthArray& lhs, const CharWidthArray& rhs) +{ + return lhs.maArray == rhs.maArray; +} + +std::ostream& operator<<(std::ostream& rStrm, const CharWidthArray& rCharWidthArray) +{ + const std::vector<sal_Int32>& rArray(rCharWidthArray.maArray); + sal_Int32 nLen = rArray.size(); + rStrm << "{ "; + for (sal_Int32 i = 0; i < nLen; ++i) + { + rStrm << rArray[i]; + rStrm << (i < nLen - 1 ? ", " : " "); + } + rStrm << "}"; + return rStrm; +} + +void CharWidthArray::ConvertToKernArray() +{ + for (std::size_t i = 1; i < maArray.size(); ++i) + maArray[i] += maArray[i - 1]; +} + +void CharWidthArray::ConvertToCharWidths() +{ + for (sal_Int32 i = maArray.size() - 1; i > 0; --i) + maArray[i] -= maArray[i - 1]; +} + +/// Convert maArray to kern array values, then invoke the function, and convert it back. +template <typename Function> void CharWidthArray::InvokeWithKernArray(Function f) +{ + ConvertToKernArray(); + f(); + ConvertToCharWidths(); +} +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSpaceDistributionHalfSpace) +{ + // Related to: tdf#149017 + static const OUStringLiteral aText = u"ne del pro"; + CharWidthArray aActual{ 720, 639, 360, 720, 639, 400, 360, 720, 480, 720 }; + CharWidthArray aExpected{ 720, 851, 573, 720, 639, 612, 573, 720, 480, 720 }; + + aActual.InvokeWithKernArray( + [&] { Justify::SpaceDistribution(aActual.maArray, aText, 0, 10, 425, 0, false); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSpaceDistributionNoHalfSpace) +{ + // Related to: tdf#149017 + static const OUStringLiteral aText = u"ne del pro"; + CharWidthArray aActual{ 720, 639, 360, 720, 639, 400, 360, 720, 480, 720 }; + CharWidthArray aExpected{ 720, 639, 785, 720, 639, 400, 785, 720, 480, 720 }; + + aActual.InvokeWithKernArray( + [&] { Justify::SpaceDistribution(aActual.maArray, aText, 0, 10, 425, 0, true); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSpaceDistributionUnicodeIVS) +{ + // Related to: tdf#148594 + static const OUStringLiteral aText + = u"\u9B54\u9AD8\u4E00\U000E01E1\u4E08\u4F55\u9B54\u9AD8\u4E00\U000E01E1"; + CharWidthArray aActual{ 1600, 1600, 1600, 0, 0, 1600, 1600, 1600, 1600, 1600, 0, 0 }; + CharWidthArray aExpected{ 1800, 1800, 1800, 0, 0, 1800, 1800, 1800, 1800, 1800, 0, 0 }; + aActual.InvokeWithKernArray( + [&] { Justify::SpaceDistribution(aActual.maArray, aText, 0, 12, 0, 200, false); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSnapToGrid) +{ + tools::Long nDelta = 0; + // "曰〈道高一尺化太平〉云云" + static const OUStringLiteral aText + = u"\u66f0\u3008\u9053\u9ad8\u4e00\u5c3a\u5316\u592a\u5e73\u3009\u4e91\u4e91"; + CharWidthArray aActual{ 880, 880, 880, 880, 880, 880, 880, 880, 880, 880, 880, 880 }; + CharWidthArray aExpected{ + 1360, 1040, 1200, 1200, 1200, 1200, 1200, 1200, 1040, 1360, 1200, 1040 + }; + aActual.InvokeWithKernArray( + [&] { nDelta = Justify::SnapToGrid(aActual.maArray, aText, 0, 12, 400, false); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); + CPPUNIT_ASSERT_EQUAL(tools::Long(160), nDelta); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSnapToGridMixWidth) +{ + // Related to: tdf#149365 + tools::Long nDelta = 0; + // "中中中ケコサシスセソカケコ" ( mixing fullwidth ideograph and half-width kana ) + static const OUStringLiteral aText + = u"\u4e2d\u4e2d\u4e2d\uff79\uff7a\uff7b\uff7c\uff7d\uff7e\uff7f\uff76\uff79\uff7a"; + CharWidthArray aActual{ 640, 640, 640, 320, 320, 320, 320, 320, 320, 320, 320, 320, 320 }; + CharWidthArray aExpected{ 800, 800, 760, 400, 400, 400, 400, 400, 400, 400, 400, 400, 360 }; + aActual.InvokeWithKernArray( + [&] { nDelta = Justify::SnapToGrid(aActual.maArray, aText, 0, 13, 400, false); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); + CPPUNIT_ASSERT_EQUAL(tools::Long(80), nDelta); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSnapToGridIVS) +{ + // Related to: tdf#149214 + tools::Long nDelta = 0; + static const OUStringLiteral aText = u"\u9053\u9ad8\u4e00\U000E01E2\u5c3a\u5316"; + + CharWidthArray aActual{ 800, 800, 800, 0, 0, 800, 800 }; + CharWidthArray aExpected{ 800, 800, 800, 0, 0, 800, 800 }; + aActual.InvokeWithKernArray( + [&] { nDelta = Justify::SnapToGrid(aActual.maArray, aText, 0, 7, 400, false); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); + CPPUNIT_ASSERT_EQUAL(tools::Long(0), nDelta); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSnapToGridEdge1) +{ + CharWidthArray aActual{ 640, 640, 640, 640, 640, 640, 320, 960 }; + CharWidthArray aExpected{ 840, 840, 840, 840, 840, 840, 440, 1240 }; + aActual.InvokeWithKernArray([&] { Justify::SnapToGridEdge(aActual.maArray, 8, 400, 40, 0); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSnapToGridEdge2) +{ + CharWidthArray aActual{ 640, 640, 640, 640, 640, 640, 320, 640 }; + CharWidthArray aExpected{ 840, 840, 840, 840, 840, 840, 440, 840 }; + aActual.InvokeWithKernArray([&] { Justify::SnapToGridEdge(aActual.maArray, 8, 100, 40, 80); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSnapToGridEdgeIVS) +{ + CharWidthArray aActual{ 640, 0, 0, 640, 640, 640, 640, 640 }; + CharWidthArray aExpected{ 840, 0, 0, 840, 840, 840, 840, 840 }; + aActual.InvokeWithKernArray([&] { Justify::SnapToGridEdge(aActual.maArray, 8, 400, 40, 0); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/qa/core/txtnode/txtnode.cxx b/sw/qa/core/txtnode/txtnode.cxx new file mode 100644 index 000000000..bd9bcd66d --- /dev/null +++ b/sw/qa/core/txtnode/txtnode.cxx @@ -0,0 +1,323 @@ +/* -*- 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 <swmodeltestbase.hxx> + +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/lok.hxx> +#include <sfx2/viewsh.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/scheduler.hxx> +#include <sfx2/lokhelper.hxx> +#include <test/lokcallback.hxx> +#include <editeng/escapementitem.hxx> + +#include <IDocumentStatistics.hxx> +#include <fmtanchr.hxx> +#include <frameformats.hxx> +#include <wrtsh.hxx> +#include <unotxdoc.hxx> +#include <docsh.hxx> +#include <formatcontentcontrol.hxx> +#include <view.hxx> +#include <edtwin.hxx> +#include <txatbase.hxx> +#include <ndtxt.hxx> +#include <textcontentcontrol.hxx> + +constexpr OUStringLiteral DATA_DIRECTORY = u"/sw/qa/core/txtnode/data/"; + +/// Covers sw/source/core/txtnode/ fixes. +class SwCoreTxtnodeTest : public SwModelTestBase +{ +}; + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testBtlrCellChinese) +{ + // Load a document with a table cell, with btlr cell direction. The cell has text which is + // classified as vertical, i.e. the glyph has the same direction in both the lrtb ("Latin") and + // tbrl ("Chinese") directions. Make sure that Chinese text is handled the same way in the btlr + // case as it's handled in the Latin case. + load(DATA_DIRECTORY, "btlr-cell-chinese.doc"); + SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get()); + SwDocShell* pShell = pTextDoc->GetDocShell(); + std::shared_ptr<GDIMetaFile> xMetaFile = pShell->GetPreviewMetaFile(); + MetafileXmlDump dumper; + xmlDocUniquePtr pXmlDoc = dumpAndParse(dumper, *xMetaFile); + assertXPath(pXmlDoc, "//font[1]", "orientation", "900"); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: false + // - Actual : true + // i.e. the glyph was rotated further, so it was upside down. + assertXPath(pXmlDoc, "//font[1]", "vertical", "false"); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testTextBoxCopyAnchor) +{ + load(DATA_DIRECTORY, "textbox-copy-anchor.docx"); + SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get()); + SwDocShell* pShell = pTextDoc->GetDocShell(); + SwWrtShell* pWrtShell = pShell->GetWrtShell(); + SwDoc aClipboard; + pWrtShell->SelAll(); + pWrtShell->Copy(aClipboard); + pWrtShell->SttEndDoc(/*bStart=*/false); + pWrtShell->Paste(aClipboard); + + const SwFrameFormats& rFormats = *pShell->GetDoc()->GetSpzFrameFormats(); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 4 + // - Actual : 6 + // i.e. 2 fly frames were copied twice. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rFormats.size()); + + SwPosition aDrawAnchor1 = *rFormats[0]->GetAnchor().GetContentAnchor(); + SwPosition aFlyAnchor1 = *rFormats[1]->GetAnchor().GetContentAnchor(); + CPPUNIT_ASSERT_EQUAL(aFlyAnchor1.nNode, aDrawAnchor1.nNode); + SwPosition aDrawAnchor2 = *rFormats[2]->GetAnchor().GetContentAnchor(); + SwPosition aFlyAnchor2 = *rFormats[3]->GetAnchor().GetContentAnchor(); + // This also failed, aFlyAnchor2 was wrong, as it got out of sync with aDrawAnchor2. + CPPUNIT_ASSERT_EQUAL(aFlyAnchor2.nNode, aDrawAnchor2.nNode); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testTextBoxNodeSplit) +{ + load(DATA_DIRECTORY, "textbox-node-split.docx"); + SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get()); + SwDocShell* pShell = pTextDoc->GetDocShell(); + SwWrtShell* pWrtShell = pShell->GetWrtShell(); + pWrtShell->SttEndDoc(/*bStart=*/false); + // Without the accompanying fix in place, this would have crashed in + // SwFlyAtContentFrame::SwClientNotify(). + pWrtShell->SplitNode(); +} + +namespace +{ +struct ViewCallback +{ + int m_nInvalidations = 0; + + static void callback(int nType, const char* pPayload, void* pData); + void callbackImpl(int nType, const char* pPayload); +}; + +void ViewCallback::callback(int nType, const char* pPayload, void* pData) +{ + static_cast<ViewCallback*>(pData)->callbackImpl(nType, pPayload); +} + +void ViewCallback::callbackImpl(int nType, const char* /*pPayload*/) +{ + switch (nType) + { + case LOK_CALLBACK_INVALIDATE_TILES: + { + ++m_nInvalidations; + } + break; + } +} +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testTitleFieldInvalidate) +{ + // Set up LOK to track invalidations. + comphelper::LibreOfficeKit::setActive(true); + + // Given a document with a title field: + load(DATA_DIRECTORY, "title-field-invalidate.fodt"); + SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get()); + pTextDoc->initializeForTiledRendering({}); + SwDocShell* pShell = pTextDoc->GetDocShell(); + SwDoc* pDoc = pShell->GetDoc(); + SwWrtShell* pWrtShell = pShell->GetWrtShell(); + pWrtShell->SttEndDoc(/*bStt=*/false); + ViewCallback aCallback; + TestLokCallbackWrapper aCallbackWrapper(&ViewCallback::callback, &aCallback); + pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&aCallbackWrapper); + aCallbackWrapper.setLOKViewId(SfxLokHelper::getView(pWrtShell->GetSfxViewShell())); + Scheduler::ProcessEventsToIdle(); + aCallback.m_nInvalidations = 0; + + // When typing to the document: + pWrtShell->Insert("x"); + pWrtShell->GetSfxViewShell()->flushPendingLOKInvalidateTiles(); + + // Then make sure that only the text frame at the cursor is invalidated: + pDoc->getIDocumentStatistics().GetUpdatedDocStat(/*bCompleteAsync=*/true, /*bFields=*/false); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 1 + // - Actual : 2 + // i.e. the footer was also invalidated on each keypress. + CPPUNIT_ASSERT_EQUAL(1, aCallback.m_nInvalidations); + + // Tear down LOK. + pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(nullptr); + mxComponent->dispose(); + mxComponent.clear(); + comphelper::LibreOfficeKit::setActive(false); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testFlyAnchorUndo) +{ + // Given a document with a fly frame, anchored after the last char of the document: + load(DATA_DIRECTORY, "fly-anchor-undo.odt"); + SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get()); + SwDocShell* pShell = pTextDoc->GetDocShell(); + SwDoc* pDoc = pShell->GetDoc(); + const SwFrameFormats& rSpz = *pDoc->GetSpzFrameFormats(); + sal_Int32 nExpected = rSpz[0]->GetAnchor().GetContentAnchor()->nContent.GetIndex(); + + // When deleting that last character and undoing it: + SwWrtShell* pWrtShell = pShell->GetWrtShell(); + pWrtShell->SttEndDoc(/*bStt=*/false); + pWrtShell->DelLeft(); + pWrtShell->Undo(); + + // Then make sure the anchor position after the undo is the same as the original: + sal_Int32 nActual = rSpz[0]->GetAnchor().GetContentAnchor()->nContent.GetIndex(); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 3 + // - Actual : 2 + // i.e. the anchor position was left unchanged by the undo. + CPPUNIT_ASSERT_EQUAL(nExpected, nActual); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testSplitNodeSuperscriptCopy) +{ + // Given a document with superscript text at the end of a paragraph: + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->Insert("1st"); + pWrtShell->Left(CRSR_SKIP_CHARS, /*bSelect=*/true, 2, /*bBasicCall=*/false); + SfxItemSetFixed<RES_CHRATR_ESCAPEMENT, RES_CHRATR_ESCAPEMENT> aSet(pWrtShell->GetAttrPool()); + SvxEscapementItem aItem(SvxEscapement::Superscript, RES_CHRATR_ESCAPEMENT); + aSet.Put(aItem); + pWrtShell->SetAttrSet(aSet); + + // When hitting enter at the end of the paragraph: + pWrtShell->SttEndDoc(/*bStt=*/false); + pWrtShell->SplitNode(/*bAutoFormat=*/true); + + // Then make sure that the superscript formatting doesn't appear on the next paragraph: + aSet.ClearItem(RES_CHRATR_ESCAPEMENT); + pWrtShell->GetCurAttr(aSet); + // Without the accompanying fix in place, this test would have failed, the unexpected + // superscript appeared in the next paragraph. + CPPUNIT_ASSERT(!aSet.HasItem(RES_CHRATR_ESCAPEMENT)); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testInsertDropDownContentControlTwice) +{ + // Given an already selected dropdown content control: + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->InsertContentControl(SwContentControlType::DROP_DOWN_LIST); + + // When trying to insert an inner one, make sure that we don't crash: + pWrtShell->InsertContentControl(SwContentControlType::DROP_DOWN_LIST); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testCheckboxContentControlKeyboard) +{ + // Given an already selected checkbox content control: + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->InsertContentControl(SwContentControlType::CHECKBOX); + SwEditWin& rEditWin = pWrtShell->GetView().GetEditWin(); + + // When pressing space on the keyboard: + KeyEvent aKeyEvent(' ', KEY_SPACE); + rEditWin.KeyInput(aKeyEvent); + + // Then make sure the state is toggled: + SwTextNode* pTextNode = pWrtShell->GetCursor()->GetNode().GetTextNode(); + SwTextAttr* pAttr = pTextNode->GetTextAttrForCharAt(0, RES_TXTATR_CONTENTCONTROL); + auto pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr); + auto& rFormatContentControl + = static_cast<SwFormatContentControl&>(pTextContentControl->GetAttr()); + std::shared_ptr<SwContentControl> pContentControl = rFormatContentControl.GetContentControl(); + // Without the accompanying fix in place, this test would have failed, because the state + // remained unchanged. + CPPUNIT_ASSERT(pContentControl->GetChecked()); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testDropdownContentControlKeyboard) +{ + // Given an already selected dropdown content control: + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->InsertContentControl(SwContentControlType::DROP_DOWN_LIST); + + // When checking if alt-down should open a popup: + SwTextContentControl* pTextContentControl = pWrtShell->CursorInsideContentControl(); + auto& rFormatContentControl + = static_cast<SwFormatContentControl&>(pTextContentControl->GetAttr()); + std::shared_ptr<SwContentControl> pContentControl = rFormatContentControl.GetContentControl(); + vcl::KeyCode aKeyCode(KEY_DOWN, KEY_MOD2); + bool bShouldOpen = pContentControl->ShouldOpenPopup(aKeyCode); + + // Then make sure that the answer is yes for dropdowns: + // Without the accompanying fix in place, this test would have failed, the dropdown popup was + // mouse-only. + CPPUNIT_ASSERT(bShouldOpen); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testPictureContentControlKeyboard) +{ + // Given an already selected picture content control: + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->InsertContentControl(SwContentControlType::PICTURE); + pWrtShell->GotoObj(/*bNext=*/true, GotoObjFlags::Any); + + // When checking if enter should trigger the file picker: + const SwFrameFormat* pFlyFormat = pWrtShell->GetFlyFrameFormat(); + const SwFormatAnchor& rFormatAnchor = pFlyFormat->GetAnchor(); + const SwPosition* pAnchorPos = rFormatAnchor.GetContentAnchor(); + SwTextNode* pTextNode = pAnchorPos->nNode.GetNode().GetTextNode(); + SwTextAttr* pAttr = pTextNode->GetTextAttrAt(pAnchorPos->nContent.GetIndex(), + RES_TXTATR_CONTENTCONTROL, SwTextNode::PARENT); + auto pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr); + auto& rFormatContentControl + = static_cast<SwFormatContentControl&>(pTextContentControl->GetAttr()); + std::shared_ptr<SwContentControl> pContentControl = rFormatContentControl.GetContentControl(); + bool bIsInteracting = pContentControl->IsInteractingCharacter('\r'); + + // Then make sure that the answer is yes for pictures: + // Without the accompanying fix in place, this test would have failed, the picture replacement + // file-picker was mouse-only. + CPPUNIT_ASSERT(bIsInteracting); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testDateContentControlKeyboard) +{ + // Given an already selected date content control: + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->InsertContentControl(SwContentControlType::DATE); + + // When checking if alt-down should open a popup: + SwTextContentControl* pTextContentControl = pWrtShell->CursorInsideContentControl(); + auto& rFormatContentControl + = static_cast<SwFormatContentControl&>(pTextContentControl->GetAttr()); + std::shared_ptr<SwContentControl> pContentControl = rFormatContentControl.GetContentControl(); + vcl::KeyCode aKeyCode(KEY_DOWN, KEY_MOD2); + bool bShouldOpen = pContentControl->ShouldOpenPopup(aKeyCode); + + // Then make sure that the answer is yes for date: + // Without the accompanying fix in place, this test would have failed, the date popup was + // mouse-only. + CPPUNIT_ASSERT(bShouldOpen); +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |