sw/qa/uibase/wrtsh/wrtsh.cxx | 67 ++++++++++++++++++++++++++++++++++++++ sw/source/core/inc/txtfrm.hxx | 2 + sw/source/core/text/itratr.cxx | 5 ++ sw/source/uibase/wrtsh/delete.cxx | 23 +++++++++++++ 4 files changed, 97 insertions(+)
New commits: commit 7715ba6e5a3f878d6fe2aa5b15a236ade8fd07e4 Author: Miklos Vajna <[email protected]> AuthorDate: Tue Sep 3 08:17:23 2024 +0200 Commit: Aron Budea <[email protected]> CommitDate: Tue Jan 21 15:35:35 2025 +0100 tdf#162507 sw floattable: fix not wanted join of two different split anchors Open the bugdoc, go to the end of the "Cannot press Delete here" paragraph, press delete, layout hangs. The trouble seems to be that this creates a doc model where multiple multi-page floating tables are anchored to the same paragraph, which is not something the layout supports. We explicitly avoid this at import time from DOCX since commit 01ad8ec4bb5425446e95dbada81de435646824b4 (sw floattable: fix lost tables around a floating table from DOCX, 2023-06-05), but there was no mechanism to prevent the UI creating the same unsupported doc model. Fix the problem by extending SwWrtShell::DelRight() to prevent joining 2 paragraphs in the unwanted case, interesting Word doesn't allow deleting at the end of that paragraph, either. Regression from commit 693ad3aadbf84afa750af73c330fe7e09b38a2e7 (tdf#120262 sw floattable, legacy: go outside body only for page frame vert pos, 2023-07-31), which restricted the amount of cases where some weird legacy behavior was performed, so good to keep that side as-is. Conflicts: sw/qa/uibase/wrtsh/wrtsh.cxx Change-Id: I4d8a6702d8ac1689690fa464014c99fcd5ef7bd2 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/172780 Reviewed-by: Miklos Vajna <[email protected]> Tested-by: Jenkins (cherry picked from commit 5b0256f30ee154edb28b999bc4faba2453fc32b8) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/172762 Reviewed-by: Adolfo Jayme Barrientos <[email protected]> (cherry picked from commit 5d8ca53e218ec96f29d66e483541b7304da169cd) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/180407 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Aron Budea <[email protected]> diff --git a/sw/qa/uibase/wrtsh/wrtsh.cxx b/sw/qa/uibase/wrtsh/wrtsh.cxx index 125a80584026..1daf9c9ebd63 100644 --- a/sw/qa/uibase/wrtsh/wrtsh.cxx +++ b/sw/qa/uibase/wrtsh/wrtsh.cxx @@ -26,6 +26,9 @@ #include <ndtxt.hxx> #include <textcontentcontrol.hxx> #include <fmtanchr.hxx> +#include <itabenum.hxx> +#include <frmmgr.hxx> +#include <formatflysplit.hxx> namespace { @@ -430,6 +433,70 @@ CPPUNIT_TEST_FIXTURE(Test, testInsertComboBoxContentControl) std::shared_ptr<SwContentControl> pContentControl = rFormatContentControl.GetContentControl(); CPPUNIT_ASSERT(pContentControl->GetComboBox()); } + +void InsertSplitFly(SwWrtShell* pWrtShell) +{ + SwPosition aInsertPos = *pWrtShell->GetCursor()->GetPoint(); + // Insert a table: + SwInsertTableOptions aTableOptions(SwInsertTableFlags::DefaultBorder, 0); + pWrtShell->InsertTable(aTableOptions, /*nRows=*/2, /*nCols=*/1); + pWrtShell->MoveTable(GotoPrevTable, fnTableStart); + pWrtShell->GoPrevCell(); + pWrtShell->Insert(u"A1"_ustr); + // Select cell: + pWrtShell->SelAll(); + // Select table: + pWrtShell->SelAll(); + // Wrap the table in a text frame: + SwFlyFrameAttrMgr aMgr(true, pWrtShell, Frmmgr_Type::TEXT, nullptr); + pWrtShell->StartAllAction(); + aMgr.InsertFlyFrame(RndStdIds::FLY_AT_PARA, aMgr.GetPos(), aMgr.GetSize()); + pWrtShell->EndAllAction(); + // Set fly properties: + pWrtShell->StartAllAction(); + SwFrameFormat* pFly = pWrtShell->GetFlyFrameFormat(); + SwAttrSet aSet(pFly->GetAttrSet()); + aSet.Put(SwFormatFlySplit(true)); + SwFormatAnchor aAnchor(RndStdIds::FLY_AT_PARA); + aAnchor.SetAnchor(&aInsertPos); + aSet.Put(aAnchor); + SwDoc* pDoc = pWrtShell->GetDoc(); + pDoc->SetAttr(aSet, *pFly); + pWrtShell->EndAllAction(); + pWrtShell->EnterStdMode(); +} + +CPPUNIT_TEST_FIXTURE(Test, testSplitFlysAnchorJoin) +{ + // Given a document with two paragraphs, each serving as an anchor of a split fly: + createSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->Insert(u"first para"_ustr); + pWrtShell->SplitNode(); + pWrtShell->Insert(u"second para"_ustr); + pWrtShell->SttEndDoc(/*bStt=*/true); + InsertSplitFly(pWrtShell); + pWrtShell->SttEndDoc(/*bStt=*/false); + pWrtShell->SttPara(); + InsertSplitFly(pWrtShell); + + // When trying to delete at the end of the first para: + pWrtShell->SttEndDoc(/*bStt=*/true); + pWrtShell->EndPara(); + pWrtShell->DelRight(); + + // Then make sure the join doesn't happen till a text node can only be an anchor for one split + // fly: + pWrtShell->SttEndDoc(/*bStt=*/true); + SwCursor* pCursor = pWrtShell->GetCursor(); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: first para + // - Actual : first parasecond para + // i.e. we did join the 2 anchors and for complex enough documents the layout never finished. + CPPUNIT_ASSERT_EQUAL(u"first para"_ustr, pCursor->GetPointNode().GetTextNode()->GetText()); + pWrtShell->SttEndDoc(/*bStt=*/false); + CPPUNIT_ASSERT_EQUAL(u"second para"_ustr, pCursor->GetPointNode().GetTextNode()->GetText()); +} } CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/source/core/inc/txtfrm.hxx b/sw/source/core/inc/txtfrm.hxx index 6e1c75c6a168..38def52dd4bf 100644 --- a/sw/source/core/inc/txtfrm.hxx +++ b/sw/source/core/inc/txtfrm.hxx @@ -807,6 +807,8 @@ public: /// with a negative vertical offset. Such frames may need explicit splitting. bool IsEmptyWithSplitFly() const; + bool HasSplitFlyDrawObjs() const; + static SwView* GetView(); void dumpAsXml(xmlTextWriterPtr writer = nullptr) const override; diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx index e29fe5ebe916..cf8871a46615 100644 --- a/sw/source/core/text/itratr.cxx +++ b/sw/source/core/text/itratr.cxx @@ -1481,6 +1481,11 @@ std::vector<SwFlyAtContentFrame*> SwTextFrame::GetSplitFlyDrawObjs() const return aObjs; } +bool SwTextFrame::HasSplitFlyDrawObjs() const +{ + return !GetSplitFlyDrawObjs().empty(); +} + SwFlyAtContentFrame* SwTextFrame::HasNonLastSplitFlyDrawObj() const { const SwTextFrame* pFollow = GetFollow(); diff --git a/sw/source/uibase/wrtsh/delete.cxx b/sw/source/uibase/wrtsh/delete.cxx index 46b0a40cca5d..5e13bd799817 100644 --- a/sw/source/uibase/wrtsh/delete.cxx +++ b/sw/source/uibase/wrtsh/delete.cxx @@ -36,6 +36,8 @@ #include <osl/diagnose.h> #include <doc.hxx> #include <IDocumentRedlineAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <txtfrm.hxx> inline void SwWrtShell::OpenMark() { @@ -336,6 +338,7 @@ bool SwWrtShell::DelRight(bool const isReplaceHeuristic) // #108049# Save the startnode of the current cell const SwStartNode* pSNdOld = pWasInTableNd ? GetCursor()->GetPointNode().FindTableBoxStartNode() : nullptr; + SwTextNode* pOldTextNode = GetCursor()->GetPointNode().GetTextNode(); bool bCheckDelFull = SelectionType::Text & nSelection && SwCursorShell::IsSttPara(); bool bDelFull = false; bool bDoNothing = false; @@ -365,6 +368,7 @@ bool SwWrtShell::DelRight(bool const isReplaceHeuristic) } // restore cursor + SwTextNode* pNewTextNode = GetCursor()->GetPointNode().GetTextNode(); SwCursorShell::Pop(SwCursorShell::PopMode::DeleteCurrent); if (bDelFull) @@ -372,6 +376,25 @@ bool SwWrtShell::DelRight(bool const isReplaceHeuristic) DelFullPara(); UpdateAttr(); } + + if (pOldTextNode && pNewTextNode && pNewTextNode != pOldTextNode) + { + SwRootFrame* pLayout = mxDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + if (pLayout) + { + auto pOldFrame = static_cast<SwTextFrame*>(pOldTextNode->getLayoutFrame(pLayout)); + auto pNewFrame = static_cast<SwTextFrame*>(pNewTextNode->getLayoutFrame(pLayout)); + if (pOldFrame && pNewFrame && pOldFrame->HasSplitFlyDrawObjs() && pNewFrame->HasSplitFlyDrawObjs()) + { + // We have a selection where both the old and the new position is an anchor + // for a potentially split fly. Don't allow join of the nodes in this case, + // since the layout supports multiple anchors for one split fly, but it + // doesn't support the usage of the same anchor for multiple split flys. + bDoNothing = true; + } + } + } + if (bDelFull || bDoNothing) break; }
