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;