sw/inc/crsrsh.hxx                |    2 
 sw/inc/doc.hxx                   |    1 
 sw/source/core/crsr/crsrsh.cxx   |   38 ++++-
 sw/source/core/docnode/ndtbl.cxx |  262 ++++++++++++++++++++-------------------
 sw/source/core/edit/eddel.cxx    |   15 +-
 sw/source/core/edit/edglss.cxx   |    8 -
 6 files changed, 181 insertions(+), 145 deletions(-)

New commits:
commit f66d5331c1f55dfc88b1439f1b8e10ebe41d3f98
Author:     Michael Stahl <michael.st...@allotropia.de>
AuthorDate: Mon Jun 5 20:24:45 2023 +0200
Commit:     Michael Stahl <michael.st...@allotropia.de>
CommitDate: Tue Jun 6 12:02:41 2023 +0200

    tdf#155685 sw: fix crash on undo of ExtendedSelectAll with table at end
    
    While the selection with table at the end works for Copy, unfortunately
    it doesn't work for Delete, because SwUndoDelete can't handle it,
    and an empty SwTableNode+SwEndNode pair remains.
    
    This needs some extra code, extract a SwDoc::DelTable() out of
    SwDoc::DeleteRowCol() and call it from SwEditShell::DeleteSel() to
    create separate SwUndoDelete objects per table, which appears to work.
    
    (regression from commit d81379db730a163c5ff75d4f3a3cddbd7b5eddda)
    
    Change-Id: I1534b100d31bc279bce298d3c2bd235cffc1f9d5
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152628
    Tested-by: Jenkins
    Reviewed-by: Michael Stahl <michael.st...@allotropia.de>

diff --git a/sw/inc/crsrsh.hxx b/sw/inc/crsrsh.hxx
index 6efdceee2838..cfba5cb852c1 100644
--- a/sw/inc/crsrsh.hxx
+++ b/sw/inc/crsrsh.hxx
@@ -332,7 +332,7 @@ public:
     // only for usage in special cases allowed!
     void ExtendedSelectAll(bool bFootnotes = true);
     /// If ExtendedSelectAll() was called and selection didn't change since 
then.
-    SwNode const* ExtendedSelectedAll() const;
+    ::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode*>>> 
ExtendedSelectedAll() const;
     enum class StartsWith { None, Table, HiddenPara };
     /// If document body starts with a table or starts/ends with hidden 
paragraph.
     StartsWith StartsWith_();
diff --git a/sw/inc/doc.hxx b/sw/inc/doc.hxx
index c3ac513c4b52..c729e0b2a983 100644
--- a/sw/inc/doc.hxx
+++ b/sw/inc/doc.hxx
@@ -1210,6 +1210,7 @@ public:
 
     // Delete Columns/Rows in table.
     enum class RowColMode { DeleteRow = 0, DeleteColumn = 1, DeleteProtected = 
2 };
+    void DelTable(SwTableNode * pTable);
     bool DeleteRowCol(const SwSelBoxes& rBoxes, RowColMode eMode = 
RowColMode::DeleteRow);
     void DeleteRow( const SwCursor& rCursor );
     void DeleteCol( const SwCursor& rCursor );
diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx
index c34827f092d8..325ff54d52cd 100644
--- a/sw/source/core/crsr/crsrsh.cxx
+++ b/sw/source/core/crsr/crsrsh.cxx
@@ -819,11 +819,12 @@ static typename SwCursorShell::StartsWith 
EndsWith(SwStartNode const& rStart)
 
 // return the node that is the start of the extended selection (to include 
table
 // or section start nodes; looks like extending for end nodes is not required)
-SwNode const* SwCursorShell::ExtendedSelectedAll() const
+::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode*>>>
+SwCursorShell::ExtendedSelectedAll() const
 {
     if (m_pTableCursor)
     {
-        return nullptr;
+        return {};
     }
 
     SwNodes& rNodes = GetDoc()->GetNodes();
@@ -834,27 +835,48 @@ SwNode const* SwCursorShell::ExtendedSelectedAll() const
     SwContentNode* pStart = rNodes.GoNext(&nNode);
     if (!pStart)
     {
-        return nullptr;
+        return {};
     }
 
     nNode = *pStartNode->EndOfSectionNode();
     SwContentNode* pEnd = SwNodes::GoPrevious(&nNode);
     if (!pEnd)
     {
-        return nullptr;
+        return {};
     }
 
     SwPosition aStart(*pStart, 0);
     SwPosition aEnd(*pEnd, pEnd->Len());
     if (!(aStart == *pShellCursor->Start() && aEnd == *pShellCursor->End()))
     {
-        return nullptr;
+        return {};
     }
 
+    auto const ends(::EndsWith(*pStartNode));
     if (::StartsWith(*pStartNode) == StartsWith::None
-        && ::EndsWith(*pStartNode) == StartsWith::None)
+        && ends == StartsWith::None)
     {
-        return nullptr; // "ordinary" selection will work
+        return {}; // "ordinary" selection will work
+    }
+
+    ::std::vector<SwTableNode*> tablesAtEnd;
+    if (ends == StartsWith::Table)
+    {
+        SwNode * pLastNode(rNodes[pStartNode->EndOfSectionIndex() - 1]);
+        while (pLastNode->IsEndNode())
+        {
+            SwNode *const pNode(pLastNode->StartOfSectionNode());
+            if (pNode->IsTableNode())
+            {
+                tablesAtEnd.push_back(pNode->GetTableNode());
+                pLastNode = rNodes[pNode->GetIndex() - 1];
+            }
+            else if (pNode->IsSectionNode())
+            {
+                pLastNode = rNodes[pLastNode->GetIndex() - 1];
+            }
+        }
+        assert(!tablesAtEnd.empty());
     }
 
     // tdf#133990 ensure directly containing section is included in 
SwUndoDelete
@@ -867,7 +889,7 @@ SwNode const* SwCursorShell::ExtendedSelectedAll() const
 
     // pStartNode is the node that fully contains the selection - the first
     // node of the selection is the first node inside pStartNode
-    return pStartNode->GetNodes()[pStartNode->GetIndex() + 1];
+    return ::std::make_pair(rNodes[pStartNode->GetIndex() + 1], tablesAtEnd);
 }
 
 typename SwCursorShell::StartsWith SwCursorShell::StartsWith_()
diff --git a/sw/source/core/docnode/ndtbl.cxx b/sw/source/core/docnode/ndtbl.cxx
index 8e055c5242bc..089b518c1cf7 100644
--- a/sw/source/core/docnode/ndtbl.cxx
+++ b/sw/source/core/docnode/ndtbl.cxx
@@ -1915,6 +1915,140 @@ void SwDoc::DeleteCol( const SwCursor& rCursor )
     GetIDocumentUndoRedo().EndUndo(SwUndoId::COL_DELETE, nullptr);
 }
 
+void SwDoc::DelTable(SwTableNode *const pTableNd)
+{
+    bool bNewTextNd = false;
+    // Is it alone in a FlyFrame?
+    SwNodeIndex aIdx( *pTableNd, -1 );
+    const SwStartNode* pSttNd = aIdx.GetNode().GetStartNode();
+    if (pSttNd)
+    {
+        const SwNodeOffset nTableEnd = pTableNd->EndOfSectionIndex() + 1;
+        const SwNodeOffset nSectEnd = pSttNd->EndOfSectionIndex();
+        if (nTableEnd == nSectEnd)
+        {
+            if (SwFlyStartNode == pSttNd->GetStartNodeType())
+            {
+                SwFrameFormat* pFormat = pSttNd->GetFlyFormat();
+                if (pFormat)
+                {
+                    // That's the FlyFormat we're looking for
+                    getIDocumentLayoutAccess().DelLayoutFormat( pFormat );
+                    return;
+                }
+            }
+            // No Fly? Thus Header or Footer: always leave a TextNode
+            // We can forget about Undo then!
+            bNewTextNd = true;
+        }
+    }
+
+    // No Fly? Then it is a Header or Footer, so keep always a TextNode
+    ++aIdx;
+    if (GetIDocumentUndoRedo().DoesUndo())
+    {
+        GetIDocumentUndoRedo().ClearRedo();
+        SwPaM aPaM( *pTableNd->EndOfSectionNode(), aIdx.GetNode() );
+
+        if (bNewTextNd)
+        {
+            const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 );
+            GetNodes().MakeTextNode( aTmpIdx.GetNode(),
+                        getIDocumentStylePoolAccess().GetTextCollFromPool( 
RES_POOLCOLL_STANDARD ) );
+        }
+
+        // Save the cursors (UNO and otherwise)
+        SwPaM aSavePaM( *pTableNd->EndOfSectionNode() );
+        if (! aSavePaM.Move(fnMoveForward, GoInNode))
+        {
+            aSavePaM.GetMark()->Assign( *pTableNd );
+            aSavePaM.Move( fnMoveBackward, GoInNode );
+        }
+        {
+            SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode());
+            ::PaMCorrAbs(tmpPaM, *aSavePaM.GetMark());
+        }
+
+        // Move hard PageBreaks to the succeeding Node
+        bool bSavePageBreak = false, bSavePageDesc = false;
+        SwNodeOffset nNextNd = pTableNd->EndOfSectionIndex()+1;
+        SwContentNode* pNextNd = GetNodes()[ nNextNd ]->GetContentNode();
+        if (pNextNd)
+        {
+            SwFrameFormat* pTableFormat = 
pTableNd->GetTable().GetFrameFormat();
+            const SfxPoolItem *pItem;
+            if (SfxItemState::SET == pTableFormat->GetItemState(RES_PAGEDESC,
+                    false, &pItem))
+            {
+                pNextNd->SetAttr( *pItem );
+                bSavePageDesc = true;
+            }
+
+            if (SfxItemState::SET == pTableFormat->GetItemState(RES_BREAK,
+                    false, &pItem))
+            {
+                pNextNd->SetAttr( *pItem );
+                bSavePageBreak = true;
+            }
+        }
+        std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete(aPaM, 
SwDeleteFlags::Default));
+        if (bNewTextNd)
+            pUndo->SetTableDelLastNd();
+        pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc );
+        pUndo->SetTableName(pTableNd->GetTable().GetFrameFormat()->GetName());
+        GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) );
+    }
+    else
+    {
+        if (bNewTextNd)
+        {
+            const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 );
+            GetNodes().MakeTextNode( aTmpIdx.GetNode(),
+                        getIDocumentStylePoolAccess().GetTextCollFromPool( 
RES_POOLCOLL_STANDARD ) );
+        }
+
+        // Save the cursors (UNO and otherwise)
+        SwPaM aSavePaM( *pTableNd->EndOfSectionNode() );
+        if (! aSavePaM.Move(fnMoveForward, GoInNode))
+        {
+            aSavePaM.GetMark()->Assign( *pTableNd );
+            aSavePaM.Move( fnMoveBackward, GoInNode );
+        }
+        {
+            SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode());
+            ::PaMCorrAbs(tmpPaM, *aSavePaM.GetMark());
+        }
+
+        // Move hard PageBreaks to the succeeding Node
+        SwContentNode* pNextNd = GetNodes()[ pTableNd->EndOfSectionIndex()+1 
]->GetContentNode();
+        if (pNextNd)
+        {
+            SwFrameFormat* pTableFormat = 
pTableNd->GetTable().GetFrameFormat();
+            const SfxPoolItem *pItem;
+            if (SfxItemState::SET == pTableFormat->GetItemState(RES_PAGEDESC,
+                    false, &pItem))
+            {
+                pNextNd->SetAttr( *pItem );
+            }
+
+            if (SfxItemState::SET == pTableFormat->GetItemState(RES_BREAK,
+                    false, &pItem))
+            {
+                pNextNd->SetAttr( *pItem );
+            }
+        }
+
+        pTableNd->DelFrames();
+        getIDocumentContentOperations().DeleteSection( pTableNd );
+    }
+
+    if (SwFEShell* pFEShell = GetDocShell()->GetFEShell())
+        pFEShell->UpdateTableStyleFormatting();
+
+    getIDocumentState().SetModified();
+    getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) 
);
+}
+
 bool SwDoc::DeleteRowCol(const SwSelBoxes& rBoxes, RowColMode const eMode)
 {
     if (!(eMode & SwDoc::RowColMode::DeleteProtected)
@@ -1954,133 +2088,7 @@ bool SwDoc::DeleteRowCol(const SwSelBoxes& rBoxes, 
RowColMode const eMode)
         aSelBoxes[0]->GetSttIdx()-1 == nTmpIdx1 &&
         nTmpIdx2 == pTableNd->EndOfSectionIndex() )
     {
-        bool bNewTextNd = false;
-        // Is it alone in a FlyFrame?
-        SwNodeIndex aIdx( *pTableNd, -1 );
-        const SwStartNode* pSttNd = aIdx.GetNode().GetStartNode();
-        if( pSttNd )
-        {
-            const SwNodeOffset nTableEnd = pTableNd->EndOfSectionIndex() + 1;
-            const SwNodeOffset nSectEnd = pSttNd->EndOfSectionIndex();
-            if( nTableEnd == nSectEnd )
-            {
-                if( SwFlyStartNode == pSttNd->GetStartNodeType() )
-                {
-                    SwFrameFormat* pFormat = pSttNd->GetFlyFormat();
-                    if( pFormat )
-                    {
-                        // That's the FlyFormat we're looking for
-                        getIDocumentLayoutAccess().DelLayoutFormat( pFormat );
-                        return true;
-                    }
-                }
-                // No Fly? Thus Header or Footer: always leave a TextNode
-                // We can forget about Undo then!
-                bNewTextNd = true;
-            }
-        }
-
-        // No Fly? Then it is a Header or Footer, so keep always a TextNode
-        ++aIdx;
-        if (GetIDocumentUndoRedo().DoesUndo())
-        {
-            GetIDocumentUndoRedo().ClearRedo();
-            SwPaM aPaM( *pTableNd->EndOfSectionNode(), aIdx.GetNode() );
-
-            if( bNewTextNd )
-            {
-                const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 );
-                GetNodes().MakeTextNode( aTmpIdx.GetNode(),
-                            getIDocumentStylePoolAccess().GetTextCollFromPool( 
RES_POOLCOLL_STANDARD ) );
-            }
-
-            // Save the cursors (UNO and otherwise)
-            SwPaM aSavePaM( *pTableNd->EndOfSectionNode() );
-            if( ! aSavePaM.Move( fnMoveForward, GoInNode ) )
-            {
-                aSavePaM.GetMark()->Assign( *pTableNd );
-                aSavePaM.Move( fnMoveBackward, GoInNode );
-            }
-            {
-                SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode());
-                ::PaMCorrAbs(tmpPaM, *aSavePaM.GetMark());
-            }
-
-            // Move hard PageBreaks to the succeeding Node
-            bool bSavePageBreak = false, bSavePageDesc = false;
-            SwNodeOffset nNextNd = pTableNd->EndOfSectionIndex()+1;
-            SwContentNode* pNextNd = GetNodes()[ nNextNd ]->GetContentNode();
-            if( pNextNd )
-            {
-                SwFrameFormat* pTableFormat = 
pTableNd->GetTable().GetFrameFormat();
-                const SfxPoolItem *pItem;
-                if( SfxItemState::SET == pTableFormat->GetItemState( 
RES_PAGEDESC,
-                    false, &pItem ) )
-                {
-                    pNextNd->SetAttr( *pItem );
-                    bSavePageDesc = true;
-                }
-
-                if( SfxItemState::SET == pTableFormat->GetItemState( RES_BREAK,
-                    false, &pItem ) )
-                {
-                    pNextNd->SetAttr( *pItem );
-                    bSavePageBreak = true;
-                }
-            }
-            std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete(aPaM, 
SwDeleteFlags::Default));
-            if( bNewTextNd )
-                pUndo->SetTableDelLastNd();
-            pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc );
-            
pUndo->SetTableName(pTableNd->GetTable().GetFrameFormat()->GetName());
-            GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) );
-        }
-        else
-        {
-            if( bNewTextNd )
-            {
-                const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 );
-                GetNodes().MakeTextNode( aTmpIdx.GetNode(),
-                            getIDocumentStylePoolAccess().GetTextCollFromPool( 
RES_POOLCOLL_STANDARD ) );
-            }
-
-            // Save the cursors (UNO and otherwise)
-            SwPaM aSavePaM( *pTableNd->EndOfSectionNode() );
-            if( ! aSavePaM.Move( fnMoveForward, GoInNode ) )
-            {
-                aSavePaM.GetMark()->Assign( *pTableNd );
-                aSavePaM.Move( fnMoveBackward, GoInNode );
-            }
-            {
-                SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode());
-                ::PaMCorrAbs(tmpPaM, *aSavePaM.GetMark());
-            }
-
-            // Move hard PageBreaks to the succeeding Node
-            SwContentNode* pNextNd = GetNodes()[ 
pTableNd->EndOfSectionIndex()+1 ]->GetContentNode();
-            if( pNextNd )
-            {
-                SwFrameFormat* pTableFormat = 
pTableNd->GetTable().GetFrameFormat();
-                const SfxPoolItem *pItem;
-                if( SfxItemState::SET == pTableFormat->GetItemState( 
RES_PAGEDESC,
-                    false, &pItem ) )
-                    pNextNd->SetAttr( *pItem );
-
-                if( SfxItemState::SET == pTableFormat->GetItemState( RES_BREAK,
-                    false, &pItem ) )
-                    pNextNd->SetAttr( *pItem );
-            }
-
-            pTableNd->DelFrames();
-            getIDocumentContentOperations().DeleteSection( pTableNd );
-        }
-
-        if (SwFEShell* pFEShell = GetDocShell()->GetFEShell())
-            pFEShell->UpdateTableStyleFormatting();
-
-        getIDocumentState().SetModified();
-        getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 
SwNodeOffset(0) );
-
+        DelTable(pTableNd);
         return true;
     }
 
diff --git a/sw/source/core/edit/eddel.cxx b/sw/source/core/edit/eddel.cxx
index 83855ff2d99a..6082dabb9ef0 100644
--- a/sw/source/core/edit/eddel.cxx
+++ b/sw/source/core/edit/eddel.cxx
@@ -35,9 +35,9 @@
 
 void SwEditShell::DeleteSel(SwPaM& rPam, bool const isArtificialSelection, 
bool *const pUndo)
 {
-    SwNode const*const pSelectAllStart(StartsWith_() != 
SwCursorShell::StartsWith::None
+    auto const oSelectAll(StartsWith_() != SwCursorShell::StartsWith::None
         ? ExtendedSelectedAll()
-        : nullptr);
+        : ::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode 
*>>>{});
     // only for selections
     if (!rPam.HasMark()
         || (*rPam.GetPoint() == *rPam.GetMark()
@@ -53,7 +53,7 @@ void SwEditShell::DeleteSel(SwPaM& rPam, bool const 
isArtificialSelection, bool
     // 3. Point and Mark are at the document start and end, Point is in a 
table: delete selection as usual
     if( rPam.GetPointNode().FindTableNode() &&
         rPam.GetPointNode().StartOfSectionNode() !=
-        rPam.GetMarkNode().StartOfSectionNode() && pSelectAllStart == nullptr)
+        rPam.GetMarkNode().StartOfSectionNode() && !oSelectAll)
     {
         // group the Undo in the table
         if( pUndo && !*pUndo )
@@ -97,13 +97,18 @@ void SwEditShell::DeleteSel(SwPaM& rPam, bool const 
isArtificialSelection, bool
     {
         std::optional<SwPaM> pNewPam;
         SwPaM * pPam = &rPam;
-        if (pSelectAllStart)
+        if (oSelectAll)
         {
+            // tdf#155685 tables at the end must be deleted separately
+            for (SwTableNode *const pTable : oSelectAll->second)
+            {
+                GetDoc()->DelTable(pTable);
+            }
             assert(dynamic_cast<SwShellCursor*>(&rPam)); // must be corrected 
pam
             pNewPam.emplace(*rPam.GetMark(), *rPam.GetPoint());
             // Selection starts at the first para of the first cell, but we
             // want to delete the table node before the first cell as well.
-            pNewPam->Start()->Assign(*pSelectAllStart);
+            pNewPam->Start()->Assign(*oSelectAll->first);
             pPam = &*pNewPam;
         }
         // delete everything
diff --git a/sw/source/core/edit/edglss.cxx b/sw/source/core/edit/edglss.cxx
index 18e5d4ec823b..80310bab4f47 100644
--- a/sw/source/core/edit/edglss.cxx
+++ b/sw/source/core/edit/edglss.cxx
@@ -197,9 +197,9 @@ bool SwEditShell::CopySelToDoc( SwDoc& rInsDoc )
         bool bColSel = GetCursor_()->IsColumnSelection();
         if( bColSel && rInsDoc.IsClipBoard() )
             rInsDoc.SetColumnSelection( true );
-        SwNode const*const pSelectAllStart(StartsWith_() != 
SwCursorShell::StartsWith::None
+        auto const oSelectAll(StartsWith_() != SwCursorShell::StartsWith::None
             ? ExtendedSelectedAll()
-            : nullptr);
+            : ::std::optional<::std::pair<SwNode const*, 
::std::vector<SwTableNode*>>>{});
         {
             for(SwPaM& rPaM : GetCursor()->GetRingContainer())
             {
@@ -223,12 +223,12 @@ bool SwEditShell::CopySelToDoc( SwDoc& rInsDoc )
                     // for the purpose of copying, our shell cursor is not 
touched.
                     // (Otherwise we would have to restore it.)
                     SwPaM aPaM(*rPaM.GetMark(), *rPaM.GetPoint());
-                    if (pSelectAllStart)
+                    if (oSelectAll)
                     {
                         // Selection starts at the first para of the first 
cell,
                         // but we want to copy the table and the start node 
before
                         // the first cell as well.
-                        aPaM.Start()->Assign(*pSelectAllStart);
+                        aPaM.Start()->Assign(*oSelectAll->first);
                     }
                     bRet = 
GetDoc()->getIDocumentContentOperations().CopyRange( aPaM, aPos, 
SwCopyFlags::CheckPosInFly)
                         || bRet;

Reply via email to