summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Stahl <michael.stahl@allotropia.de>2023-06-05 20:24:45 +0200
committerMichael Stahl <michael.stahl@allotropia.de>2023-06-06 12:02:41 +0200
commitf66d5331c1f55dfc88b1439f1b8e10ebe41d3f98 (patch)
treecc36fa3d39d7f10d12a671dba2b05346f27edddd
parentf831b1edd87d934d74fc931a3650e3737ccfe3a8 (diff)
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.stahl@allotropia.de>
-rw-r--r--sw/inc/crsrsh.hxx2
-rw-r--r--sw/inc/doc.hxx1
-rw-r--r--sw/source/core/crsr/crsrsh.cxx38
-rw-r--r--sw/source/core/docnode/ndtbl.cxx262
-rw-r--r--sw/source/core/edit/eddel.cxx15
-rw-r--r--sw/source/core/edit/edglss.cxx8
6 files changed, 181 insertions, 145 deletions
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;