sw/qa/core/layout/flycnt.cxx | 138 ++++++++++++++++++++------------------ sw/source/core/inc/flyfrms.hxx | 2 sw/source/core/inc/rootfrm.hxx | 9 ++ sw/source/core/layout/flowfrm.cxx | 1 sw/source/core/layout/flycnt.cxx | 50 +++++++++++++ sw/source/core/layout/layact.cxx | 1 sw/source/core/layout/tabfrm.cxx | 11 +++ 7 files changed, 147 insertions(+), 65 deletions(-)
New commits: commit f6fbd9d5ff5b049112e6ca7a8943c156b3e4f411 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Fri Mar 31 08:13:15 2023 +0200 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Fri Mar 31 07:04:05 2023 +0000 sw floattable: remove empty follow flys on follow table removal - add a SwRootFrame::DeleteEmptyFlys_(), which can delete empty flys and invalidate the anchors, so the necessary text frame joins and page frame deletions happen - add a SwRootFrame::InsertEmptyFly(), which can build a to-delete list for DeleteEmptyFlys_() - add a SwFlyAtContentFrame::DelEmpty(), which can call InsertEmptyFly() for one empty fly frame - in SwTabFrame::Cut(), call DelEmpty() on the fly parent, similar to how we do it for sections - in SwLayAction::InternalAction(), call DeleteEmptyFlys() to actually delete the unnecessary fly frames Change-Id: I8d3b4ee2c07b60d6187059bb177c56a129810750 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/149815 Reviewed-by: Miklos Vajna <vmik...@collabora.com> Tested-by: Jenkins diff --git a/sw/qa/core/layout/flycnt.cxx b/sw/qa/core/layout/flycnt.cxx index daa9764325f5..09d08ff14285 100644 --- a/sw/qa/core/layout/flycnt.cxx +++ b/sw/qa/core/layout/flycnt.cxx @@ -37,8 +37,55 @@ public: : SwModelTestBase("/sw/qa/core/layout/data/") { } + + /// Creates a document with a multi-page floating table: 1 columns and 2 rows. + void Create1x2SplitFly(); }; +void Test::Create1x2SplitFly() +{ + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwPageDesc aStandard(pDoc->GetPageDesc(0)); + SwFormatFrameSize aPageSize(aStandard.GetMaster().GetFrameSize()); + // 5cm for the page height, 2cm are the top and bottom margins, so 1cm remains for the body + // frame: + aPageSize.SetHeight(2834); + aStandard.GetMaster().SetFormatAttr(aPageSize); + pDoc->ChgPageDesc(0, aStandard); + // Insert a table: + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + SwInsertTableOptions aTableOptions(SwInsertTableFlags::DefaultBorder, 0); + pWrtShell->InsertTable(aTableOptions, /*nRows=*/2, /*nCols=*/1); + pWrtShell->MoveTable(GotoPrevTable, fnTableStart); + pWrtShell->GoPrevCell(); + pWrtShell->Insert("A1"); + pWrtShell->GoNextCell(); + pWrtShell->Insert("A2"); + // 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(); + // Allow the text frame to split: + pWrtShell->StartAllAction(); + SwFrameFormats& rFlys = *pDoc->GetSpzFrameFormats(); + SwFrameFormat* pFly = rFlys[0]; + SwAttrSet aSet(pFly->GetAttrSet()); + aSet.Put(SwFormatFlySplit(true)); + pDoc->SetAttr(aSet, *pFly); + pWrtShell->EndAllAction(); + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + auto pPage1 = dynamic_cast<SwPageFrame*>(pLayout->Lower()); + CPPUNIT_ASSERT(pPage1); + // We have 2 pages: + CPPUNIT_ASSERT(pPage1->GetNext()); +} + CPPUNIT_TEST_FIXTURE(Test, testSplitFlyWithTable) { // Given a document with a multi-page floating table: @@ -235,38 +282,10 @@ CPPUNIT_TEST_FIXTURE(Test, testSplitFlyRow) CPPUNIT_TEST_FIXTURE(Test, testSplitFlyEnable) { // Given a document with a table in a textframe: - createSwDoc(); - SwDocShell* pDocShell = getSwDocShell(); - SwDoc* pDoc = getSwDoc(); - SwPageDesc aStandard(pDoc->GetPageDesc(0)); - SwFormatFrameSize aPageSize(aStandard.GetMaster().GetFrameSize()); - // 5cm for the page height, 2cm are the top and bottom margins, so 1cm remains for the body - // frame: - aPageSize.SetHeight(2834); - aStandard.GetMaster().SetFormatAttr(aPageSize); - pDoc->ChgPageDesc(0, aStandard); - // Insert a table: - SwWrtShell* pWrtShell = pDocShell->GetWrtShell(); - SwInsertTableOptions aTableOptions(SwInsertTableFlags::DefaultBorder, 0); - pWrtShell->InsertTable(aTableOptions, /*nRows=*/2, /*nCols=*/1); - pWrtShell->MoveTable(GotoPrevTable, fnTableStart); - pWrtShell->SelAll(); - // Wrap it 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(); - - // When allowing the text frame to split: - pWrtShell->StartAllAction(); - SwFrameFormats& rFlys = *pDoc->GetSpzFrameFormats(); - SwFrameFormat* pFly = rFlys[0]; - SwAttrSet aSet(pFly->GetAttrSet()); - aSet.Put(SwFormatFlySplit(true)); - pDoc->SetAttr(aSet, *pFly); - pWrtShell->EndAllAction(); + Create1x2SplitFly(); // Then make sure that the layout is updated and we have 2 pages: + SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast<SwPageFrame*>(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); @@ -548,43 +567,10 @@ CPPUNIT_TEST_FIXTURE(Test, testSplitFlyFollowHorizontalPosition) CPPUNIT_TEST_FIXTURE(Test, testCursorTraversal) { // Given a document with a multi-page floating table: - createSwDoc(); - SwDoc* pDoc = getSwDoc(); - SwPageDesc aStandard(pDoc->GetPageDesc(0)); - SwFormatFrameSize aPageSize(aStandard.GetMaster().GetFrameSize()); - // 5cm for the page height, 2cm are the top and bottom margins, so 1cm remains for the body - // frame: - aPageSize.SetHeight(2834); - aStandard.GetMaster().SetFormatAttr(aPageSize); - pDoc->ChgPageDesc(0, aStandard); - // Insert a table: - SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); - SwInsertTableOptions aTableOptions(SwInsertTableFlags::DefaultBorder, 0); - pWrtShell->InsertTable(aTableOptions, /*nRows=*/2, /*nCols=*/1); - pWrtShell->MoveTable(GotoPrevTable, fnTableStart); - pWrtShell->GoPrevCell(); - pWrtShell->Insert("A1"); - pWrtShell->GoNextCell(); - pWrtShell->Insert("A2"); - // 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(); - // Allow the text frame to split: - pWrtShell->StartAllAction(); - SwFrameFormats& rFlys = *pDoc->GetSpzFrameFormats(); - SwFrameFormat* pFly = rFlys[0]; - SwAttrSet aSet(pFly->GetAttrSet()); - aSet.Put(SwFormatFlySplit(true)); - pDoc->SetAttr(aSet, *pFly); - pWrtShell->EndAllAction(); + Create1x2SplitFly(); // When going from A1 to A2: + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); pWrtShell->GotoTable("Table1"); SwTextNode* pTextNode = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); CPPUNIT_ASSERT_EQUAL(OUString("A1"), pTextNode->GetText()); @@ -598,6 +584,28 @@ CPPUNIT_TEST_FIXTURE(Test, testCursorTraversal) // i.e. the cursor didn't get from A1 to A2. CPPUNIT_ASSERT_EQUAL(OUString("A2"), pTextNode->GetText()); } + +CPPUNIT_TEST_FIXTURE(Test, testSplitFlyRowDelete) +{ + // Given a document with a multi-page floating table: + Create1x2SplitFly(); + + // When deleting the row of A2: + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->GotoTable("Table1"); + pWrtShell->Down(/*bSelect=*/false); + SwTextNode* pTextNode = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); + // We delete the right row: + CPPUNIT_ASSERT_EQUAL(OUString("A2"), pTextNode->GetText()); + pWrtShell->DeleteRow(); + + // Then make sure we only have 1 page: + SwDoc* pDoc = getSwDoc(); + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + auto pPage1 = dynamic_cast<SwPageFrame*>(pLayout->Lower()); + CPPUNIT_ASSERT(pPage1); + CPPUNIT_ASSERT(!pPage1->GetNext()); +} } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/flyfrms.hxx b/sw/source/core/inc/flyfrms.hxx index 8d3bd95589bc..74b9c44c6c25 100644 --- a/sw/source/core/inc/flyfrms.hxx +++ b/sw/source/core/inc/flyfrms.hxx @@ -200,6 +200,8 @@ public: SwFlyAtContentFrame* GetFollow(); const SwFlyAtContentFrame* GetPrecede() const; SwFlyAtContentFrame* GetPrecede(); + /// Like Cut(), except that follow chaining is maintained. + void DelEmpty(); void dumpAsXmlAttributes(xmlTextWriterPtr pWriter) const override; }; diff --git a/sw/source/core/inc/rootfrm.hxx b/sw/source/core/inc/rootfrm.hxx index 9cf2a4f13143..0caf4012862d 100644 --- a/sw/source/core/inc/rootfrm.hxx +++ b/sw/source/core/inc/rootfrm.hxx @@ -76,6 +76,8 @@ using SwCurrShells = std::set<CurrShell*>; class SwSectionFrame; using SwDestroyList = o3tl::sorted_vector<SwSectionFrame*>; +class SwFlyFrame; +using SwFlyDestroyList = o3tl::sorted_vector<SwFlyFrame*>; /// The root element of a Writer document layout. Lower frames are expected to /// be SwPageFrame instances. @@ -172,6 +174,7 @@ class SW_DLLPUBLIC SwRootFrame final : public SwLayoutFrame SdrPage *mpDrawPage; std::unique_ptr<SwDestroyList> mpDestroy; + std::unique_ptr<SwFlyDestroyList> mpFlyDestroy; sal_uInt16 mnPhyPageNums; /// Page count sal_uInt16 mnAccessibleShells; // Number of accessible shells @@ -180,6 +183,8 @@ class SW_DLLPUBLIC SwRootFrame final : public SwLayoutFrame void ImplInvalidateBrowseWidth(); void DeleteEmptySct_(); // Destroys the registered SectionFrames + /// Destroys the registered FlyFrames. + void DeleteEmptyFlys_(); void RemoveFromList_( SwSectionFrame* pSct ); // Removes SectionFrames from the Delete List virtual void DestroyImpl() override; @@ -381,7 +386,11 @@ public: * destroyed later on or deregistered. */ void InsertEmptySct( SwSectionFrame* pDel ); + /// Empty SwFlyFrames are registered here for deletion and destroyed later if they are not + /// de-registered in the meantime. + void InsertEmptyFly(SwFlyFrame* pDel); void DeleteEmptySct() { if( mpDestroy ) DeleteEmptySct_(); } + void DeleteEmptyFlys() { if( mpFlyDestroy ) DeleteEmptyFlys_(); } void RemoveFromList( SwSectionFrame* pSct ) { if( mpDestroy ) RemoveFromList_( pSct ); } #ifdef DBG_UTIL bool IsInDelList( SwSectionFrame* pSct ) const; diff --git a/sw/source/core/layout/flowfrm.cxx b/sw/source/core/layout/flowfrm.cxx index 0aa9c6f14534..444e597eaa3b 100644 --- a/sw/source/core/layout/flowfrm.cxx +++ b/sw/source/core/layout/flowfrm.cxx @@ -64,6 +64,7 @@ #include <IDocumentDrawModelAccess.hxx> #include <pam.hxx> #include <ndtxt.hxx> +#include <flyfrms.hxx> bool SwFlowFrame::s_bMoveBwdJump = false; diff --git a/sw/source/core/layout/flycnt.cxx b/sw/source/core/layout/flycnt.cxx index 52eaeb6c1cd4..a0abd0bf15d3 100644 --- a/sw/source/core/layout/flycnt.cxx +++ b/sw/source/core/layout/flycnt.cxx @@ -1624,6 +1624,25 @@ SwLayoutFrame *SwFrame::GetNextFlyLeaf( MakePageType eMakePage ) return pLayLeaf; } +void SwRootFrame::DeleteEmptyFlys_() +{ + assert(mpFlyDestroy); + + while (!mpFlyDestroy->empty()) + { + SwFlyFrame* pFly = *mpFlyDestroy->begin(); + mpFlyDestroy->erase( mpFlyDestroy->begin() ); + if (!pFly->getFrameArea().HasArea() && !pFly->ContainsContent() + && !pFly->IsDeleteForbidden()) + { + SwTextFrame* pFlyAnchor = pFly->FindAnchorCharFrame(); + SwFrame::DestroyFrame(pFly); + // So that JoinFrame() is called on the precede of the anchor if it has any. + pFlyAnchor->InvalidateSize(); + } + } +} + const SwFlyAtContentFrame* SwFlyAtContentFrame::GetPrecede() const { return static_cast<const SwFlyAtContentFrame*>(SwFlowFrame::GetPrecede()); @@ -1634,6 +1653,37 @@ SwFlyAtContentFrame* SwFlyAtContentFrame::GetPrecede() return static_cast<SwFlyAtContentFrame*>(SwFlowFrame::GetPrecede()); } +void SwFlyAtContentFrame::DelEmpty() +{ + SwFlyAtContentFrame* pMaster = IsFollow() ? GetPrecede() : nullptr; + if (pMaster) + { + pMaster->SetFollow(GetFollow()); + } + SetFollow(nullptr); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height(0); + } + InvalidateObjRectWithSpaces(); + + if(getRootFrame()) + { + getRootFrame()->InsertEmptyFly(this); + } +} + +void SwRootFrame::InsertEmptyFly(SwFlyFrame* pDel) +{ + if (!mpFlyDestroy) + { + mpFlyDestroy.reset(new SwFlyDestroyList); + } + + mpFlyDestroy->insert(pDel); +} + SwLayoutFrame* SwFrame::GetPrevFlyLeaf() { auto pFly = dynamic_cast<SwFlyAtContentFrame*>(FindFlyFrame()); diff --git a/sw/source/core/layout/layact.cxx b/sw/source/core/layout/layact.cxx index 971d2761639e..583ca4a3b4db 100644 --- a/sw/source/core/layout/layact.cxx +++ b/sw/source/core/layout/layact.cxx @@ -549,6 +549,7 @@ void SwLayAction::InternalAction(OutputDevice* pRenderContext) const bool bTakeShortcut = !IsIdle() && !IsComplete() && IsShortCut(pPage); m_pRoot->DeleteEmptySct(); + m_pRoot->DeleteEmptyFlys(); if (lcl_isLayoutLooping()) return; if (!bTakeShortcut) diff --git a/sw/source/core/layout/tabfrm.cxx b/sw/source/core/layout/tabfrm.cxx index ef169a209212..9bb630f181a0 100644 --- a/sw/source/core/layout/tabfrm.cxx +++ b/sw/source/core/layout/tabfrm.cxx @@ -3826,6 +3826,7 @@ void SwTabFrame::Cut() { OSL_ENSURE( !pUp->IsFootnoteFrame(), "Table in Footnote." ); SwSectionFrame *pSct = nullptr; + SwFlyFrame *pFly = nullptr; // #126020# - adjust check for empty section // #130797# - correct fix #126020# if ( !pUp->Lower() && pUp->IsInSct() && @@ -3838,6 +3839,16 @@ void SwTabFrame::Cut() pSct->InvalidateSize_(); } } + else if (!pUp->Lower() && pUp->IsInFly() && + !(pFly = pUp->FindFlyFrame())->ContainsContent() && + !pFly->ContainsAny()) + { + if (pUp == pFly && pFly->IsFlySplitAllowed()) + { + auto pFlyAtContent = static_cast<SwFlyAtContentFrame*>(pFly); + pFlyAtContent->DelEmpty(); + } + } // table-in-footnote: delete empty footnote frames (like SwContentFrame::Cut) else if (!pUp->Lower() && pUp->IsFootnoteFrame() && !pUp->IsColLocked()) {