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())
         {

Reply via email to