android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / source / core / undo / undel.cxx
blobcd1b3b1d918271143eff631bfbd84bbbe1bf27ad
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <UndoDelete.hxx>
22 #include <libxml/xmlwriter.h>
23 #include <editeng/formatbreakitem.hxx>
25 #include <hintids.hxx>
26 #include <osl/diagnose.h>
27 #include <rtl/ustrbuf.hxx>
28 #include <unotools/charclass.hxx>
29 #include <frmfmt.hxx>
30 #include <fmtanchr.hxx>
31 #include <doc.hxx>
32 #include <UndoManager.hxx>
33 #include <IDocumentRedlineAccess.hxx>
34 #include <IDocumentStylePoolAccess.hxx>
35 #include <swtable.hxx>
36 #include <swundo.hxx>
37 #include <pam.hxx>
38 #include <ndtxt.hxx>
39 #include <UndoCore.hxx>
40 #include <rolbck.hxx>
41 #include <poolfmt.hxx>
42 #include <mvsave.hxx>
43 #include <docary.hxx>
44 #include <frmtool.hxx>
45 #include <txtfrm.hxx>
46 #include <rootfrm.hxx>
47 #include <strings.hrc>
48 #include <frameformats.hxx>
49 #include <fmtpdsc.hxx>
50 #include <vector>
52 // DELETE
53 /* lcl_MakeAutoFrames has to call MakeFrames for objects bounded "AtChar"
54 ( == AUTO ), if the anchor frame has be moved via MoveNodes(..) and
55 DelFrames(..)
57 static void lcl_MakeAutoFrames(const sw::FrameFormats<sw::SpzFrameFormat*>& rSpzs, SwNodeOffset nMovedIndex )
59 for(auto pSpz: rSpzs)
61 const SwFormatAnchor* pAnchor = &pSpz->GetAnchor();
62 if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR)
64 const SwNode* pAnchorNode = pAnchor->GetAnchorNode();
65 if( pAnchorNode && nMovedIndex == pAnchorNode->GetIndex() )
66 pSpz->MakeFrames();
71 static SwTextNode * FindFirstAndNextNode(SwDoc & rDoc, SwUndRng const& rRange,
72 SwRedlineSaveDatas const& rRedlineSaveData,
73 SwTextNode *& o_rpFirstMergedDeletedTextNode)
75 // redlines are corrected now to exclude the deleted node
76 assert(rRange.m_nEndContent == 0);
77 SwNodeOffset nEndOfRedline(0);
78 for (size_t i = 0; i < rRedlineSaveData.size(); ++i)
80 auto const& rRedline(rRedlineSaveData[i]);
81 if (rRedline.m_nSttNode <= rRange.m_nSttNode
82 // coverity[copy_paste_error : FALSE] : m_nEndNode is intentional here
83 && rRedline.m_nSttNode < rRange.m_nEndNode
84 && rRange.m_nEndNode <= rRedline.m_nEndNode
85 && rRedline.GetType() == RedlineType::Delete)
87 nEndOfRedline = rRedline.m_nEndNode;
88 o_rpFirstMergedDeletedTextNode = rDoc.GetNodes()[rRedline.m_nSttNode]->GetTextNode();
89 assert(rRange.m_nSttNode == rRange.m_nEndNode - 1); // otherwise this needs to iterate more RL to find the first node?
90 break;
93 if (nEndOfRedline)
95 assert(o_rpFirstMergedDeletedTextNode);
96 SwTextNode * pNextNode(nullptr);
97 for (SwNodeOffset i = rRange.m_nEndNode; /* i <= nEndOfRedline */; ++i)
99 SwNode *const pNode(rDoc.GetNodes()[i]);
100 assert(!pNode->IsEndNode()); // cannot be both leaving section here *and* overlapping redline
101 if (pNode->IsStartNode())
103 i = pNode->EndOfSectionIndex(); // will be incremented again
105 else if (pNode->IsTextNode())
107 pNextNode = pNode->GetTextNode();
108 break;
111 assert(pNextNode);
112 return pNextNode;
114 else
116 return nullptr;
120 static void DelFullParaMoveFrames(SwDoc & rDoc, SwUndRng const& rRange,
121 SwRedlineSaveDatas const& rRedlineSaveData)
123 SwTextNode * pFirstMergedDeletedTextNode(nullptr);
124 SwTextNode *const pNextNode = FindFirstAndNextNode(rDoc, rRange,
125 rRedlineSaveData, pFirstMergedDeletedTextNode);
126 if (!pNextNode)
127 return;
129 std::vector<SwTextFrame*> frames;
130 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pFirstMergedDeletedTextNode);
131 for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
133 if (pFrame->getRootFrame()->HasMergedParas())
135 assert(pFrame->GetMergedPara());
136 assert(pFrame->GetMergedPara()->pFirstNode == pFirstMergedDeletedTextNode);
137 assert(pNextNode->GetIndex() <= pFrame->GetMergedPara()->pLastNode->GetIndex());
138 frames.push_back(pFrame);
141 for (SwTextFrame *const pFrame : frames)
143 // sw_redlinehide: don't need FrameMode::Existing here
144 // because everything from pNextNode onwards is already
145 // correctly hidden
146 pFrame->RegisterToNode(*pNextNode, true);
150 // SwUndoDelete has to perform a deletion and to record anything that is needed
151 // to restore the situation before the deletion. Unfortunately a part of the
152 // deletion will be done after calling this Ctor, this has to be kept in mind!
153 // In this Ctor only the complete paragraphs will be deleted, the joining of
154 // the first and last paragraph of the selection will be handled outside this
155 // function.
156 // Here are the main steps of the function:
157 // 1. Deletion/recording of content indices of the selection: footnotes, fly
158 // frames and bookmarks
159 // Step 1 could shift all nodes by deletion of footnotes => nNdDiff will be set.
160 // 2. If the paragraph where the selection ends, is the last content of a
161 // section so that this section becomes empty when the paragraphs will be
162 // joined we have to do some smart actions ;-) The paragraph will be moved
163 // outside the section and replaced by a dummy text node, the complete
164 // section will be deleted in step 3. The difference between replacement
165 // dummy and original is nReplacementDummy.
166 // 3. Moving complete selected nodes into the UndoArray. Before this happens the
167 // selection has to be extended if there are sections which would become
168 // empty otherwise. BTW: sections will be moved into the UndoArray if they
169 // are complete part of the selection. Sections starting or ending outside
170 // of the selection will not be removed from the DocNodeArray even they got
171 // a "dummy"-copy in the UndoArray.
172 // 4. We have to anticipate the joining of the two paragraphs if the start
173 // paragraph is inside a section and the end paragraph not. Then we have to
174 // move the paragraph into this section and to record this in nSectDiff.
175 SwUndoDelete::SwUndoDelete(
176 SwPaM& rPam,
177 SwDeleteFlags const flags,
178 bool bFullPara,
179 bool bCalledByTableCpy )
180 : SwUndo(SwUndoId::DELETE, &rPam.GetDoc()),
181 SwUndRng( rPam ),
182 m_nNode(0),
183 m_nNdDiff(0),
184 m_nSectDiff(0),
185 m_nReplaceDummy(0),
186 m_nSetPos(0),
187 m_bGroup( false ),
188 m_bBackSp( false ),
189 m_bJoinNext( false ),
190 m_bTableDelLastNd( false ),
191 // bFullPara is set e.g. if an empty paragraph before a table is deleted
192 m_bDelFullPara( bFullPara ),
193 m_bResetPgDesc( false ),
194 m_bResetPgBrk( false ),
195 m_bFromTableCopy( bCalledByTableCpy )
196 , m_DeleteFlags(flags)
198 assert(!m_bDelFullPara || !(m_DeleteFlags & SwDeleteFlags::ArtificialSelection));
200 m_bCacheComment = false;
202 SwDoc& rDoc = rPam.GetDoc();
204 if( !rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
206 m_pRedlSaveData.reset(new SwRedlineSaveDatas);
207 if( !FillSaveData( rPam, *m_pRedlSaveData ))
209 m_pRedlSaveData.reset();
213 if( !m_pHistory )
214 m_pHistory.reset( new SwHistory );
216 // delete all footnotes for now
217 auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
219 // Step 1. deletion/record of content indices
220 if( m_bDelFullPara )
222 OSL_ENSURE( rPam.HasMark(), "PaM without Mark" );
223 DelContentIndex( *rPam.GetMark(), *rPam.GetPoint(),
224 DelContentType(DelContentType::AllMask | DelContentType::CheckNoCntnt) );
226 ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
227 DelBookmarks(pStt->GetNode(), pEnd->GetNode());
229 else
231 DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(),
232 DelContentType::AllMask
233 | ((m_DeleteFlags & SwDeleteFlags::ArtificialSelection) ? DelContentType::Replace : DelContentType(0)));
234 ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
235 if (m_nEndNode - m_nSttNode > SwNodeOffset(1)) // check for fully selected nodes
237 SwNodeIndex const start(pStt->GetNode(), +1);
238 DelBookmarks(start.GetNode(), pEnd->GetNode());
242 m_nSetPos = m_pHistory ? m_pHistory->Count() : 0;
244 // Is already anything deleted?
245 m_nNdDiff = m_nSttNode - pStt->GetNodeIndex();
247 m_bJoinNext = !bFullPara && pEnd == rPam.GetPoint();
248 m_bBackSp = !bFullPara && !m_bJoinNext;
250 SwTextNode *pSttTextNd = nullptr, *pEndTextNd = nullptr;
251 if( !bFullPara )
253 pSttTextNd = pStt->GetNode().GetTextNode();
254 pEndTextNd = m_nSttNode == m_nEndNode
255 ? pSttTextNd
256 : pEnd->GetNode().GetTextNode();
258 else if (m_pRedlSaveData)
260 DelFullParaMoveFrames(rDoc, *this, *m_pRedlSaveData);
263 bool bMoveNds = *pStt != *pEnd // any area still existent?
264 && ( SaveContent( pStt, pEnd, pSttTextNd, pEndTextNd ) || m_bFromTableCopy );
266 if( pSttTextNd && pEndTextNd && pSttTextNd != pEndTextNd )
268 // two different TextNodes, thus save also the TextFormatCollection
269 m_pHistory->Add( pSttTextNd->GetTextColl(),pStt->GetNodeIndex(), SwNodeType::Text );
270 m_pHistory->Add( pEndTextNd->GetTextColl(),pEnd->GetNodeIndex(), SwNodeType::Text );
272 if( !m_bJoinNext ) // Selection from bottom to top
274 // When using JoinPrev() all AUTO-PageBreak's will be copied
275 // correctly. To restore them with UNDO, Auto-PageBreak of the
276 // EndNode needs to be reset. Same for PageDesc and ColBreak.
277 if( pEndTextNd->HasSwAttrSet() )
279 SwRegHistory aRegHist( *pEndTextNd, m_pHistory.get() );
280 if( SfxItemState::SET == pEndTextNd->GetpSwAttrSet()->GetItemState(
281 RES_BREAK, false ) )
282 pEndTextNd->ResetAttr( RES_BREAK );
283 if( pEndTextNd->HasSwAttrSet() &&
284 SfxItemState::SET == pEndTextNd->GetpSwAttrSet()->GetItemState(
285 RES_PAGEDESC, false ) )
286 pEndTextNd->ResetAttr( RES_PAGEDESC );
291 // Move now also the PaM. The SPoint is at the beginning of a SSelection.
292 if( pEnd == rPam.GetPoint() && ( !bFullPara || pSttTextNd || pEndTextNd ) )
293 rPam.Exchange();
295 if( !pSttTextNd && !pEndTextNd )
296 rPam.GetPoint()->Adjust(SwNodeOffset(-1));
297 rPam.DeleteMark(); // the SPoint is in the selection
299 if( !pEndTextNd )
300 m_nEndContent = 0;
301 if( !pSttTextNd )
302 m_nSttContent = 0;
304 if( bMoveNds ) // Do Nodes exist that need to be moved?
306 SwNodes& rNds = rDoc.GetUndoManager().GetUndoNodes();
307 SwNodes& rDocNds = rDoc.GetNodes();
308 SwNodeRange aRg( rDocNds, m_nSttNode - m_nNdDiff, m_nEndNode - m_nNdDiff );
309 if( !bFullPara && !pEndTextNd &&
310 aRg.aEnd.GetNode() != rDoc.GetNodes().GetEndOfContent() )
312 SwNode* pNode = aRg.aEnd.GetNode().StartOfSectionNode();
313 if( pNode->GetIndex() >= m_nSttNode - m_nNdDiff )
314 ++aRg.aEnd; // Deletion of a complete table
316 SwNode* pTmpNd;
317 // Step 2: Expand selection if necessary
318 if( m_bJoinNext || bFullPara )
320 // If all content of a section will be moved into Undo, the section
321 // itself should be moved completely.
322 while( aRg.aEnd.GetIndex() + 2 < rDocNds.Count() &&
323 ( (pTmpNd = rDocNds[ aRg.aEnd.GetIndex()+1 ])->IsEndNode() &&
324 pTmpNd->StartOfSectionNode()->IsSectionNode() &&
325 pTmpNd->StartOfSectionNode()->GetIndex() >= aRg.aStart.GetIndex() ) )
326 ++aRg.aEnd;
327 m_nReplaceDummy = aRg.aEnd.GetIndex() + m_nNdDiff - m_nEndNode;
328 if( m_nReplaceDummy )
329 { // The selection has been expanded, because
330 ++aRg.aEnd;
331 if( pEndTextNd )
333 // The end text node has to leave the (expanded) selection
334 // The dummy is needed because MoveNodes deletes empty
335 // sections
336 ++m_nReplaceDummy;
337 SwNodeRange aMvRg( *pEndTextNd, SwNodeOffset(0), *pEndTextNd, SwNodeOffset(1) );
338 SwPosition aSplitPos( *pEndTextNd );
339 ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
340 rDoc.getIDocumentContentOperations().SplitNode( aSplitPos, false );
341 rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aEnd.GetNode() );
342 --aRg.aEnd;
344 else
345 m_nReplaceDummy = SwNodeOffset(0);
348 if( m_bBackSp || bFullPara )
350 // See above, the selection has to be expanded if there are "nearly
351 // empty" sections and a replacement dummy has to be set if needed.
352 while( SwNodeOffset(1) < aRg.aStart.GetIndex() &&
353 ( (pTmpNd = rDocNds[ aRg.aStart.GetIndex()-1 ])->IsSectionNode() &&
354 pTmpNd->EndOfSectionIndex() < aRg.aEnd.GetIndex() ) )
355 --aRg.aStart;
356 if( pSttTextNd )
358 m_nReplaceDummy = m_nSttNode - m_nNdDiff - aRg.aStart.GetIndex();
359 if( m_nReplaceDummy )
361 SwNodeRange aMvRg( *pSttTextNd, SwNodeOffset(0), *pSttTextNd, SwNodeOffset(1) );
362 SwPosition aSplitPos( *pSttTextNd );
363 ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
364 rDoc.getIDocumentContentOperations().SplitNode( aSplitPos, false );
365 rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aStart.GetNode() );
366 --aRg.aStart;
371 if( m_bFromTableCopy )
373 if( !pEndTextNd )
375 if( pSttTextNd )
376 ++aRg.aStart;
377 else if( !bFullPara && !aRg.aEnd.GetNode().IsContentNode() )
378 --aRg.aEnd;
381 else if (pSttTextNd && (pEndTextNd || pSttTextNd->GetText().getLength()))
382 ++aRg.aStart;
384 // Step 3: Moving into UndoArray...
385 m_nNode = rNds.GetEndOfContent().GetIndex();
386 rDocNds.MoveNodes( aRg, rNds, rNds.GetEndOfContent() );
387 m_oMvStt.emplace( rNds, m_nNode );
388 // remember difference!
389 m_nNode = rNds.GetEndOfContent().GetIndex() - m_nNode;
391 if( pSttTextNd && pEndTextNd )
393 //Step 4: Moving around sections
394 m_nSectDiff = aRg.aEnd.GetIndex() - aRg.aStart.GetIndex();
395 // nSect is the number of sections which starts(ends) between start
396 // and end node of the selection. The "loser" paragraph has to be
397 // moved into the section(s) of the "winner" paragraph
398 if( m_nSectDiff )
400 if( m_bJoinNext )
402 SwNodeRange aMvRg( *pEndTextNd, SwNodeOffset(0), *pEndTextNd, SwNodeOffset(1) );
403 rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aStart.GetNode() );
405 else
407 SwNodeRange aMvRg( *pSttTextNd, SwNodeOffset(0), *pSttTextNd, SwNodeOffset(1) );
408 rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aEnd.GetNode() );
412 if( m_nSectDiff || m_nReplaceDummy )
413 lcl_MakeAutoFrames( *rDoc.GetSpzFrameFormats(),
414 m_bJoinNext ? pEndTextNd->GetIndex() : pSttTextNd->GetIndex() );
416 else
417 m_nNode = SwNodeOffset(0); // moved no node -> no difference at the end
419 // Are there any Nodes that got deleted before that (FootNotes
420 // have ContentNodes)?
421 if( !pSttTextNd && !pEndTextNd )
423 m_nNdDiff = m_nSttNode - rPam.GetPoint()->GetNodeIndex() - (bFullPara ? 0 : 1);
424 rPam.Move( fnMoveForward, GoInNode );
426 else
428 m_nNdDiff = m_nSttNode;
429 if( m_nSectDiff && m_bBackSp )
430 m_nNdDiff += m_nSectDiff;
431 m_nNdDiff -= rPam.GetPoint()->GetNodeIndex();
434 // is a history necessary here at all?
435 if( m_pHistory && !m_pHistory->Count() )
436 m_pHistory.reset();
439 bool SwUndoDelete::SaveContent( const SwPosition* pStt, const SwPosition* pEnd,
440 SwTextNode* pSttTextNd, SwTextNode* pEndTextNd )
442 SwNodeOffset nNdIdx = pStt->GetNodeIndex();
443 // 1 - copy start in Start-String
444 if( pSttTextNd )
446 bool bOneNode = m_nSttNode == m_nEndNode;
447 SwRegHistory aRHst( *pSttTextNd, m_pHistory.get() );
448 // always save all text atttibutes because of possibly overlapping
449 // areas of on/off
450 m_pHistory->CopyAttr( pSttTextNd->GetpSwpHints(), nNdIdx,
451 0, pSttTextNd->GetText().getLength(), true );
452 if( !bOneNode && pSttTextNd->HasSwAttrSet() )
453 m_pHistory->CopyFormatAttr( *pSttTextNd->GetpSwAttrSet(), nNdIdx );
455 // the length might have changed (!!Fields!!)
456 sal_Int32 nLen = (bOneNode
457 ? pEnd->GetContentIndex()
458 : pSttTextNd->GetText().getLength())
459 - pStt->GetContentIndex();
461 // delete now also the text (all attribute changes are added to
462 // UNDO history)
463 m_aSttStr = pSttTextNd->GetText().copy(m_nSttContent, nLen);
464 pSttTextNd->EraseText( *pStt, nLen );
465 if( pSttTextNd->GetpSwpHints() )
466 pSttTextNd->GetpSwpHints()->DeRegister();
468 // METADATA: store
469 bool emptied( !m_aSttStr->isEmpty() && !pSttTextNd->Len() );
470 if (!bOneNode || emptied) // merging may overwrite xmlids...
472 m_pMetadataUndoStart = emptied
473 ? pSttTextNd->CreateUndoForDelete()
474 : pSttTextNd->CreateUndo();
477 if( bOneNode )
478 return false; // stop moving more nodes
481 // 2 - copy end into End-String
482 if( pEndTextNd )
484 SwContentIndex aEndIdx( pEndTextNd );
485 nNdIdx = pEnd->GetNodeIndex();
486 SwRegHistory aRHst( *pEndTextNd, m_pHistory.get() );
488 // always save all text atttibutes because of possibly overlapping
489 // areas of on/off
490 m_pHistory->CopyAttr( pEndTextNd->GetpSwpHints(), nNdIdx, 0,
491 pEndTextNd->GetText().getLength(), true );
493 if( pEndTextNd->HasSwAttrSet() )
494 m_pHistory->CopyFormatAttr( *pEndTextNd->GetpSwAttrSet(), nNdIdx );
496 // delete now also the text (all attribute changes are added to
497 // UNDO history)
498 m_aEndStr = pEndTextNd->GetText().copy( 0, pEnd->GetContentIndex() );
499 pEndTextNd->EraseText( aEndIdx, pEnd->GetContentIndex() );
500 if( pEndTextNd->GetpSwpHints() )
501 pEndTextNd->GetpSwpHints()->DeRegister();
503 // METADATA: store
504 bool emptied = !m_aEndStr->isEmpty() && !pEndTextNd->Len();
506 m_pMetadataUndoEnd = emptied
507 ? pEndTextNd->CreateUndoForDelete()
508 : pEndTextNd->CreateUndo();
511 // if there are only two Nodes then we're done
512 if( ( pSttTextNd || pEndTextNd ) && m_nSttNode + 1 == m_nEndNode )
513 return false; // do not move any Node
515 return true; // move Nodes lying in between
518 bool SwUndoDelete::CanGrouping( SwDoc& rDoc, const SwPaM& rDelPam )
520 // Is Undo greater than one Node (that is Start and EndString)?
521 if( !m_aSttStr || m_aSttStr->isEmpty() || m_aEndStr )
522 return false;
524 // only the deletion of single char's can be condensed
525 if( m_nSttNode != m_nEndNode || ( !m_bGroup && m_nSttContent+1 != m_nEndContent ))
526 return false;
528 auto [pStt, pEnd] = rDelPam.StartEnd(); // SwPosition*
530 if( pStt->GetNode() != pEnd->GetNode() ||
531 pStt->GetContentIndex()+1 != pEnd->GetContentIndex() ||
532 pEnd->GetNodeIndex() != m_nSttNode )
533 return false;
535 // Distinguish between BackSpace and Delete because the Undo array needs to
536 // be constructed differently!
537 if( pEnd->GetContentIndex() == m_nSttContent )
539 if( m_bGroup && !m_bBackSp ) return false;
540 m_bBackSp = true;
542 else if( pStt->GetContentIndex() == m_nSttContent )
544 if( m_bGroup && m_bBackSp ) return false;
545 m_bBackSp = false;
547 else
548 return false;
550 // are both Nodes (Node/Undo array) TextNodes at all?
551 SwTextNode * pDelTextNd = pStt->GetNode().GetTextNode();
552 if( !pDelTextNd ) return false;
554 sal_Int32 nUChrPos = m_bBackSp ? 0 : m_aSttStr->getLength()-1;
555 sal_Unicode cDelChar = pDelTextNd->GetText()[ pStt->GetContentIndex() ];
556 CharClass& rCC = GetAppCharClass();
557 if( ( CH_TXTATR_BREAKWORD == cDelChar || CH_TXTATR_INWORD == cDelChar ) ||
558 rCC.isLetterNumeric( OUString( cDelChar ), 0 ) !=
559 rCC.isLetterNumeric( *m_aSttStr, nUChrPos ) )
560 return false;
562 // tdf#132725 - if at-char/at-para flys would be deleted, don't group!
563 // DelContentIndex() would be called at the wrong time here, the indexes
564 // in the stored SwHistoryTextFlyCnt would be wrong when Undo is invoked
565 if (IsFlySelectedByCursor(rDoc, *pStt, *pEnd))
567 return false;
571 SwRedlineSaveDatas aTmpSav;
572 const bool bSaved = FillSaveData( rDelPam, aTmpSav, false );
574 bool bOk = ( !m_pRedlSaveData && !bSaved ) ||
575 ( m_pRedlSaveData && bSaved &&
576 SwUndo::CanRedlineGroup( *m_pRedlSaveData, aTmpSav, m_bBackSp ));
577 // aTmpSav.DeleteAndDestroyAll();
578 if( !bOk )
579 return false;
581 rDoc.getIDocumentRedlineAccess().DeleteRedline( rDelPam, false, RedlineType::Any );
584 // Both 'deletes' can be consolidated, so 'move' the related character
585 if( m_bBackSp )
586 m_nSttContent--; // BackSpace: add char to array!
587 else
589 m_nEndContent++; // Delete: attach char at the end
590 nUChrPos++;
592 m_aSttStr = m_aSttStr->replaceAt( nUChrPos, 0, rtl::OUStringChar(cDelChar) );
593 pDelTextNd->EraseText( *pStt, 1 );
595 m_bGroup = true;
596 return true;
599 SwUndoDelete::~SwUndoDelete()
601 if( m_oMvStt ) // Delete also the selection from UndoNodes array
603 // Insert saves content in IconSection
604 m_oMvStt->GetNode().GetNodes().Delete( *m_oMvStt, m_nNode );
605 m_oMvStt.reset();
607 m_pRedlSaveData.reset();
610 static SwRewriter lcl_RewriterFromHistory(SwHistory & rHistory)
612 SwRewriter aRewriter;
614 bool bDone = false;
616 for ( sal_uInt16 n = 0; n < rHistory.Count(); n++)
618 OUString aDescr = rHistory[n]->GetDescription();
620 if (!aDescr.isEmpty())
622 aRewriter.AddRule(UndoArg2, aDescr);
624 bDone = true;
625 break;
629 if (! bDone)
631 aRewriter.AddRule(UndoArg2, SwResId(STR_FIELD));
634 return aRewriter;
637 static bool lcl_IsSpecialCharacter(sal_Unicode nChar)
639 switch (nChar)
641 case CH_TXTATR_BREAKWORD:
642 case CH_TXTATR_INWORD:
643 case CH_TXTATR_TAB:
644 case CH_TXTATR_NEWLINE:
645 case CH_TXT_ATR_INPUTFIELDSTART:
646 case CH_TXT_ATR_INPUTFIELDEND:
647 case CH_TXT_ATR_FORMELEMENT:
648 case CH_TXT_ATR_FIELDSTART:
649 case CH_TXT_ATR_FIELDSEP:
650 case CH_TXT_ATR_FIELDEND:
651 return true;
653 default:
654 break;
657 return false;
660 static OUString lcl_DenotedPortion(std::u16string_view rStr, sal_Int32 nStart, sal_Int32 nEnd, bool bQuoted)
662 OUString aResult;
664 auto nCount = nEnd - nStart;
665 if (nCount > 0)
667 sal_Unicode cLast = rStr[nEnd - 1];
668 if (lcl_IsSpecialCharacter(cLast))
670 switch(cLast)
672 case CH_TXTATR_TAB:
673 aResult = SwResId(STR_UNDO_TABS, nCount);
675 break;
676 case CH_TXTATR_NEWLINE:
677 aResult = SwResId(STR_UNDO_NLS, nCount);
679 break;
681 case CH_TXTATR_INWORD:
682 case CH_TXTATR_BREAKWORD:
683 aResult = SwRewriter::GetPlaceHolder(UndoArg2);
684 break;
686 case CH_TXT_ATR_INPUTFIELDSTART:
687 case CH_TXT_ATR_INPUTFIELDEND:
688 case CH_TXT_ATR_FORMELEMENT:
689 case CH_TXT_ATR_FIELDSTART:
690 case CH_TXT_ATR_FIELDSEP:
691 case CH_TXT_ATR_FIELDEND:
692 break; // nothing?
694 default:
695 assert(!"unexpected special character");
696 break;
698 SwRewriter aRewriter;
699 aRewriter.AddRule(UndoArg1, OUString::number(nCount));
700 aResult = aRewriter.Apply(aResult);
702 else if (bQuoted)
704 aResult = SwResId(STR_START_QUOTE) +
705 rStr.substr(nStart, nCount) +
706 SwResId(STR_END_QUOTE);
708 else
709 aResult = rStr.substr(nStart, nCount);
712 return aResult;
715 OUString DenoteSpecialCharacters(std::u16string_view aStr, bool bQuoted)
717 OUStringBuffer aResult;
719 if (!aStr.empty())
721 bool bStart = false;
722 sal_Int32 nStart = 0;
723 sal_Unicode cLast = 0;
725 for( size_t i = 0; i < aStr.size(); i++)
727 if (lcl_IsSpecialCharacter(aStr[i]))
729 if (cLast != aStr[i])
730 bStart = true;
733 else
735 if (lcl_IsSpecialCharacter(cLast))
736 bStart = true;
739 if (bStart)
741 aResult.append(lcl_DenotedPortion(aStr, nStart, i, bQuoted));
743 nStart = i;
744 bStart = false;
747 cLast = aStr[i];
750 aResult.append(lcl_DenotedPortion(aStr, nStart, aStr.size(), bQuoted));
752 else
753 aResult = SwRewriter::GetPlaceHolder(UndoArg2);
755 return aResult.makeStringAndClear();
758 SwRewriter SwUndoDelete::GetRewriter() const
760 SwRewriter aResult;
762 if (m_nNode != SwNodeOffset(0))
764 if (!m_sTableName.isEmpty())
767 SwRewriter aRewriter;
768 aRewriter.AddRule(UndoArg1, SwResId(STR_START_QUOTE));
769 aRewriter.AddRule(UndoArg2, m_sTableName);
770 aRewriter.AddRule(UndoArg3, SwResId(STR_END_QUOTE));
772 OUString sTmp = aRewriter.Apply(SwResId(STR_TABLE_NAME));
773 aResult.AddRule(UndoArg1, sTmp);
775 else
776 aResult.AddRule(UndoArg1, SwResId(STR_PARAGRAPHS));
778 else
780 OUString aStr;
782 if (m_aSttStr && m_aEndStr && m_aSttStr->isEmpty() &&
783 m_aEndStr->isEmpty())
785 aStr = SwResId(STR_PARAGRAPH_UNDO);
787 else
789 std::optional<OUString> aTmpStr;
790 if (m_aSttStr)
791 aTmpStr = m_aSttStr;
792 else if (m_aEndStr)
793 aTmpStr = m_aEndStr;
795 if (aTmpStr)
797 aStr = DenoteSpecialCharacters(*aTmpStr);
799 else
801 aStr = SwRewriter::GetPlaceHolder(UndoArg2);
805 aStr = ShortenString(aStr, nUndoStringLength, SwResId(STR_LDOTS));
806 if (m_pHistory)
808 SwRewriter aRewriter = lcl_RewriterFromHistory(*m_pHistory);
809 aStr = aRewriter.Apply(aStr);
812 aResult.AddRule(UndoArg1, aStr);
815 return aResult;
818 // Every object, anchored "AtContent" will be reanchored at rPos
819 static void lcl_ReAnchorAtContentFlyFrames(const sw::FrameFormats<sw::SpzFrameFormat*>& rSpzs, const SwPosition &rPos, SwNodeOffset nOldIdx )
821 const SwFormatAnchor* pAnchor;
822 for(auto pSpz: rSpzs)
824 pAnchor = &pSpz->GetAnchor();
825 if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA)
827 SwNode* pAnchorNode = pAnchor->GetAnchorNode();
828 if( pAnchorNode && nOldIdx == pAnchorNode->GetIndex() )
830 SwFormatAnchor aAnch( *pAnchor );
831 aAnch.SetAnchor( &rPos );
832 pSpz->SetFormatAttr( aAnch );
838 void SwUndoDelete::UndoImpl(::sw::UndoRedoContext & rContext)
840 SwDoc& rDoc = rContext.GetDoc();
842 SwNodeOffset nCalcStt = m_nSttNode - m_nNdDiff;
844 if( m_nSectDiff && m_bBackSp )
845 nCalcStt += m_nSectDiff;
847 SwNodeIndex aIdx(rDoc.GetNodes(), nCalcStt);
848 SwNode* pInsNd = &aIdx.GetNode();
849 SwNode* pMovedNode = nullptr;
851 { // code block so that SwPosition is detached when deleting a Node
852 SwPosition aPos( aIdx );
853 if( !m_bDelFullPara )
855 assert(!m_bTableDelLastNd || pInsNd->IsTextNode());
856 if( pInsNd->IsTableNode() )
858 pInsNd = rDoc.GetNodes().MakeTextNode( aIdx.GetNode(),
859 rDoc.GetDfltTextFormatColl() );
860 --aIdx;
861 aPos.Assign( *pInsNd->GetContentNode(), m_nSttContent );
863 else
865 if( pInsNd->IsContentNode() )
866 aPos.SetContent( m_nSttContent );
867 if( !m_bTableDelLastNd )
868 pInsNd = nullptr; // do not delete Node!
871 else
872 pInsNd = nullptr; // do not delete Node!
874 bool bNodeMove = SwNodeOffset(0) != m_nNode;
876 if( m_aEndStr )
878 // discard attributes since they all saved!
879 SwTextNode * pTextNd;
880 if (!m_bDelFullPara && aPos.GetNode().IsSectionNode())
881 { // tdf#134250 section node wasn't deleted; but aPos must point to it in bNodeMove case below
882 assert(m_nSttContent == 0);
883 assert(!m_aSttStr);
884 pTextNd = rDoc.GetNodes()[aPos.GetNodeIndex() + 1]->GetTextNode();
886 else
888 pTextNd = aPos.GetNode().GetTextNode();
891 if( pTextNd && pTextNd->HasSwAttrSet() )
892 pTextNd->ResetAllAttr();
894 if( pTextNd && pTextNd->GetpSwpHints() )
895 pTextNd->ClearSwpHintsArr( true );
897 if( m_aSttStr && !m_bFromTableCopy )
899 SwNodeOffset nOldIdx = aPos.GetNodeIndex();
900 rDoc.getIDocumentContentOperations().SplitNode( aPos, false );
901 // After the split all objects are anchored at the first
902 // paragraph, but the pHistory of the fly frame formats relies
903 // on anchoring at the start of the selection
904 // => selection backwards needs a correction.
905 if( m_bBackSp )
906 lcl_ReAnchorAtContentFlyFrames(*rDoc.GetSpzFrameFormats(), aPos, nOldIdx);
907 pTextNd = aPos.GetNode().GetTextNode();
909 assert(pTextNd); // else where does m_aEndStr come from?
910 if( pTextNd )
912 SwContentIndex aTmpIdx(pTextNd, aPos.GetContentIndex());
913 OUString const ins( pTextNd->InsertText(*m_aEndStr, aTmpIdx,
914 SwInsertFlags::NOHINTEXPAND) );
915 assert(ins.getLength() == m_aEndStr->getLength()); // must succeed
916 (void) ins;
917 // METADATA: restore
918 pTextNd->RestoreMetadata(m_pMetadataUndoEnd);
921 else if (m_aSttStr && bNodeMove && pInsNd == nullptr)
923 SwTextNode * pNd = aPos.GetNode().GetTextNode();
924 if( pNd )
926 if (m_nSttContent < pNd->GetText().getLength())
928 SwNodeOffset nOldIdx = aPos.GetNodeIndex();
929 rDoc.getIDocumentContentOperations().SplitNode( aPos, false );
930 if( m_bBackSp )
931 lcl_ReAnchorAtContentFlyFrames(*rDoc.GetSpzFrameFormats(), aPos, nOldIdx);
933 else
934 aPos.Adjust(SwNodeOffset(+1));
937 if( m_nSectDiff )
939 SwNodeOffset nMoveIndex = aPos.GetNodeIndex();
940 SwNodeOffset nDiff(0);
941 if( m_bJoinNext )
943 nMoveIndex += m_nSectDiff + 1;
944 pMovedNode = &aPos.GetNode();
946 else
948 nMoveIndex -= m_nSectDiff + 1;
949 ++nDiff;
951 SwNodeIndex aMvIdx(rDoc.GetNodes(), nMoveIndex);
952 SwNodeRange aRg( aPos.GetNode(), SwNodeOffset(0) - nDiff, aPos.GetNode(), SwNodeOffset(1) - nDiff );
953 aPos.Adjust(SwNodeOffset(-1));
954 if( !m_bJoinNext )
955 pMovedNode = &aPos.GetNode();
956 rDoc.GetNodes().MoveNodes(aRg, rDoc.GetNodes(), aMvIdx.GetNode());
957 aPos.Adjust(SwNodeOffset(+1));
960 if( bNodeMove )
962 SwNodeRange aRange( *m_oMvStt, SwNodeOffset(0), *m_oMvStt, m_nNode );
963 SwNodeIndex aCopyIndex( aPos.GetNode(), -1 );
964 rDoc.GetUndoManager().GetUndoNodes().Copy_(aRange, aPos.GetNode(),
965 // sw_redlinehide: delay creating frames: the flags on the
966 // nodes aren't necessarily up-to-date, and the redlines
967 // from m_pRedlSaveData aren't applied yet...
968 false);
970 if( m_nReplaceDummy )
972 SwNodeOffset nMoveIndex;
973 if( m_bJoinNext )
975 nMoveIndex = m_nEndNode - m_nNdDiff;
976 aPos.Assign( nMoveIndex + m_nReplaceDummy );
978 else
980 aPos.Assign( aCopyIndex );
981 nMoveIndex = aPos.GetNodeIndex() + m_nReplaceDummy + 1;
983 SwNodeIndex aMvIdx(rDoc.GetNodes(), nMoveIndex);
984 SwNodeRange aRg( aPos.GetNode(), SwNodeOffset(0), aPos.GetNode(), SwNodeOffset(1) );
985 pMovedNode = &aPos.GetNode();
986 // tdf#131684 without deleting frames
987 rDoc.GetNodes().MoveNodes(aRg, rDoc.GetNodes(), aMvIdx.GetNode(), false);
988 rDoc.GetNodes().Delete( aMvIdx);
992 if( m_aSttStr )
994 aPos.Assign( m_nSttNode - m_nNdDiff + ( m_bJoinNext ? SwNodeOffset(0) : m_nReplaceDummy ) );
995 SwTextNode * pTextNd = aPos.GetNode().GetTextNode();
996 // If more than a single Node got deleted, also all "Node"
997 // attributes were saved
998 if (pTextNd != nullptr)
1000 if( pTextNd->HasSwAttrSet() && bNodeMove && !m_aEndStr )
1001 pTextNd->ResetAllAttr();
1003 if( pTextNd->GetpSwpHints() )
1004 pTextNd->ClearSwpHintsArr( true );
1006 // SectionNode mode and selection from top to bottom:
1007 // -> in StartNode is still the rest of the Join => delete
1008 aPos.SetContent( m_nSttContent );
1009 pTextNd->SetInSwUndo(true);
1010 OUString const ins( pTextNd->InsertText(*m_aSttStr, aPos,
1011 SwInsertFlags::NOHINTEXPAND) );
1012 pTextNd->SetInSwUndo(false);
1013 assert(ins.getLength() == m_aSttStr->getLength()); // must succeed
1014 (void) ins;
1015 // METADATA: restore
1016 pTextNd->RestoreMetadata(m_pMetadataUndoStart);
1020 if( m_pHistory )
1022 m_pHistory->TmpRollback(&rDoc, m_nSetPos, false);
1023 if( m_nSetPos ) // there were Footnodes/FlyFrames
1025 // are there others than these ones?
1026 if( m_nSetPos < m_pHistory->Count() )
1028 // if so save the attributes of the others
1029 SwHistory aHstr;
1030 aHstr.Move( 0, m_pHistory.get(), m_nSetPos );
1031 m_pHistory->Rollback(&rDoc);
1032 m_pHistory->Move( 0, &aHstr );
1034 else
1036 m_pHistory->Rollback(&rDoc);
1037 m_pHistory.reset();
1042 if( m_bResetPgDesc || m_bResetPgBrk )
1044 sal_uInt16 nStt = m_bResetPgDesc ? sal_uInt16(RES_PAGEDESC) : sal_uInt16(RES_BREAK);
1045 sal_uInt16 nEnd = m_bResetPgBrk ? sal_uInt16(RES_BREAK) : sal_uInt16(RES_PAGEDESC);
1047 SwNode* pNode = rDoc.GetNodes()[ m_nEndNode + 1 ];
1048 if( pNode->IsContentNode() )
1049 static_cast<SwContentNode*>(pNode)->ResetAttr( nStt, nEnd );
1050 else if( pNode->IsTableNode() )
1051 static_cast<SwTableNode*>(pNode)->GetTable().GetFrameFormat()->ResetFormatAttr( nStt, nEnd );
1054 // delete the temporarily added Node
1055 if (pInsNd && !m_bTableDelLastNd)
1057 assert(&aIdx.GetNode() == pInsNd);
1058 rDoc.GetNodes().Delete( aIdx );
1060 if( m_pRedlSaveData )
1061 SetSaveData(rDoc, *m_pRedlSaveData);
1063 SwNodeOffset delFullParaEndNode(m_nEndNode);
1064 if (m_bDelFullPara && m_pRedlSaveData)
1066 SwTextNode * pFirstMergedDeletedTextNode(nullptr);
1067 SwTextNode *const pNextNode = FindFirstAndNextNode(rDoc, *this,
1068 *m_pRedlSaveData, pFirstMergedDeletedTextNode);
1069 if (pNextNode)
1071 bool bNonMerged(false);
1072 std::vector<SwTextFrame*> frames;
1073 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pNextNode);
1074 for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
1076 if (pFrame->getRootFrame()->HasMergedParas())
1078 frames.push_back(pFrame);
1080 else
1082 bNonMerged = true;
1085 for (SwTextFrame *const pFrame : frames)
1087 // could either destroy the text frames, or move them...
1088 // destroying them would have the advantage that we don't
1089 // need special code to *exclude* pFirstMergedDeletedTextNode
1090 // from MakeFrames for the layouts in Hide mode but not
1091 // layouts in Show mode ...
1092 // ... except that MakeFrames won't create them then :(
1093 pFrame->RegisterToNode(*pFirstMergedDeletedTextNode);
1094 assert(pFrame->GetMergedPara());
1095 assert(!bNonMerged); // delFullParaEndNode is such an awful hack
1096 (void) bNonMerged;
1097 delFullParaEndNode = pFirstMergedDeletedTextNode->GetIndex();
1101 else if (m_aSttStr && (!m_bFromTableCopy || SwNodeOffset(0) != m_nNode))
1103 // only now do we have redlines in the document again; fix up the split
1104 // frames
1105 SwTextNode *const pStartNode(aIdx.GetNodes()[m_nSttNode]->GetTextNode());
1106 assert(pStartNode);
1107 sw::RecreateStartTextFrames(*pStartNode);
1110 // create frames after SetSaveData has recreated redlines
1111 if (SwNodeOffset(0) != m_nNode)
1113 // tdf#136453 only if section nodes at the start
1114 if (m_bBackSp && m_nReplaceDummy != SwNodeOffset(0))
1116 // tdf#134252 *first* create outer section frames
1117 // note: text node m_nSttNode currently has frame with an upper;
1118 // there's a hack in InsertCnt_() to move it below new section frame
1119 SwNode& start(*rDoc.GetNodes()[m_nSttNode - m_nReplaceDummy]);
1120 SwNode& end(*rDoc.GetNodes()[m_nSttNode]); // exclude m_nSttNode
1121 ::MakeFrames(&rDoc, start, end);
1123 // tdf#121031 if the start node is a text node, it already has a frame;
1124 // if it's a table, it does not
1125 // tdf#109376 exception: end on non-text-node -> start node was inserted
1126 assert(!m_bDelFullPara || (m_nSectDiff == SwNodeOffset(0)));
1127 SwNode& start(*rDoc.GetNodes()[m_nSttNode +
1128 ((m_bDelFullPara || !rDoc.GetNodes()[m_nSttNode]->IsTextNode() || pInsNd)
1129 ? 0 : 1)]);
1130 // don't include end node in the range: it may have been merged already
1131 // by the start node, or it may be merged by one of the moved nodes,
1132 // but if it isn't merged, its current frame(s) should be good...
1133 SwNode& end(*rDoc.GetNodes()[ m_bDelFullPara
1134 ? delFullParaEndNode
1135 // tdf#147310 SwDoc::DeleteRowCol() may delete whole table - end must be node following table!
1136 : (m_nEndNode + (rDoc.GetNodes()[m_nSttNode]->IsTableNode() && rDoc.GetNodes()[m_nEndNode]->IsEndNode() ? 1 : 0))]);
1137 ::MakeFrames(&rDoc, start, end);
1140 if (pMovedNode)
1141 { // probably better do this after creating all frames
1142 lcl_MakeAutoFrames(*rDoc.GetSpzFrameFormats(), pMovedNode->GetIndex());
1145 // tdf#134021 only after MakeFrames(), because it may be the only node
1146 // that has layout frames
1147 if (pInsNd && m_bTableDelLastNd)
1149 assert(&aIdx.GetNode() == pInsNd);
1150 SwPaM tmp(aIdx, aIdx);
1151 rDoc.getIDocumentContentOperations().DelFullPara(tmp);
1154 AddUndoRedoPaM(rContext, true);
1157 void SwUndoDelete::RedoImpl(::sw::UndoRedoContext & rContext)
1159 SwPaM & rPam = AddUndoRedoPaM(rContext);
1160 SwDoc& rDoc = rPam.GetDoc();
1162 if( m_pRedlSaveData )
1164 const bool bSuccess = FillSaveData(rPam, *m_pRedlSaveData);
1165 OSL_ENSURE(bSuccess,
1166 "SwUndoDelete::Redo: used to have redline data, but now none?");
1167 if (!bSuccess)
1169 m_pRedlSaveData.reset();
1173 if( !m_bDelFullPara )
1175 // tdf#128739 correct cursors but do not delete bookmarks yet
1176 ::PaMCorrAbs(rPam, *rPam.End());
1177 SetPaM(rPam);
1179 if( !m_bJoinNext ) // then restore selection from bottom to top
1180 rPam.Exchange();
1183 if( m_pHistory ) // are the attributes saved?
1185 m_pHistory->SetTmpEnd( m_pHistory->Count() );
1186 SwHistory aHstr;
1187 aHstr.Move( 0, m_pHistory.get() );
1189 if( m_bDelFullPara )
1191 OSL_ENSURE( rPam.HasMark(), "PaM without Mark" );
1192 DelContentIndex( *rPam.GetMark(), *rPam.GetPoint(),
1193 DelContentType(DelContentType::AllMask | DelContentType::CheckNoCntnt) );
1195 DelBookmarks(rPam.GetMark()->GetNode(), rPam.GetPoint()->GetNode());
1197 else
1199 DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(),
1200 DelContentType::AllMask
1201 | ((m_DeleteFlags & SwDeleteFlags::ArtificialSelection) ? DelContentType::Replace : DelContentType(0)));
1203 m_nSetPos = m_pHistory ? m_pHistory->Count() : 0;
1205 m_pHistory->Move( m_nSetPos, &aHstr );
1207 else
1209 if( m_bDelFullPara )
1211 OSL_ENSURE( rPam.HasMark(), "PaM without Mark" );
1212 DelContentIndex( *rPam.GetMark(), *rPam.GetPoint(),
1213 DelContentType(DelContentType::AllMask | DelContentType::CheckNoCntnt) );
1215 DelBookmarks( rPam.GetMark()->GetNode(), rPam.GetPoint()->GetNode() );
1217 else
1219 DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(),
1220 DelContentType::AllMask
1221 | ((m_DeleteFlags & SwDeleteFlags::ArtificialSelection) ? DelContentType::Replace : DelContentType(0)));
1223 m_nSetPos = m_pHistory ? m_pHistory->Count() : 0;
1226 if( !m_aSttStr && !m_aEndStr )
1228 if (m_bDelFullPara && m_pRedlSaveData)
1230 DelFullParaMoveFrames(rDoc, *this, *m_pRedlSaveData);
1233 SwNode& rSttNd = ( m_bDelFullPara || m_bJoinNext )
1234 ? rPam.GetMark()->GetNode()
1235 : rPam.GetPoint()->GetNode();
1236 SwTableNode* pTableNd = rSttNd.GetTableNode();
1237 if( pTableNd )
1239 if( m_bTableDelLastNd )
1241 // than add again a Node at the end
1242 const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 );
1243 rDoc.GetNodes().MakeTextNode( aTmpIdx.GetNode(),
1244 rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) );
1247 SwContentNode* pNextNd = rDoc.GetNodes()[
1248 pTableNd->EndOfSectionIndex()+1 ]->GetContentNode();
1249 if( pNextNd )
1251 SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat();
1253 if( const SwFormatPageDesc* pItem = pTableFormat->GetItemIfSet( RES_PAGEDESC,
1254 false ) )
1255 pNextNd->SetAttr( *pItem );
1257 if( const SvxFormatBreakItem* pItem = pTableFormat->GetItemIfSet( RES_BREAK,
1258 false ) )
1259 pNextNd->SetAttr( *pItem );
1261 pTableNd->DelFrames();
1263 else if (*rPam.GetMark() == *rPam.GetPoint())
1264 { // paragraph with only footnote or as-char fly, delete that
1265 // => DelContentIndex has already deleted it! nothing to do here
1266 assert(m_nEndNode == m_nSttNode);
1267 return;
1270 // avoid asserts from ~SwContentIndexReg for deleted nodes
1271 SwPaM aTmp(*rPam.End());
1272 if (!aTmp.Move(fnMoveForward, GoInNode))
1274 *aTmp.GetPoint() = *rPam.Start();
1275 aTmp.Move(fnMoveBackward, GoInNode);
1277 // coverity[copy_paste_error : FALSE] : GetNode() is intentional on both branches
1278 assert(aTmp.GetPoint()->GetNode() != rPam.GetPoint()->GetNode()
1279 && aTmp.GetPoint()->GetNode() != rPam.GetMark()->GetNode());
1280 ::PaMCorrAbs(rPam, *aTmp.GetPoint());
1282 rPam.DeleteMark();
1284 rDoc.GetNodes().Delete( rSttNd, m_nEndNode - m_nSttNode );
1286 else if( m_bDelFullPara )
1288 assert(!"dead code");
1289 // The Pam was incremented by one at Point (== end) to provide space
1290 // for UNDO. This now needs to be reverted!
1291 rPam.End()->Adjust(SwNodeOffset(-1));
1292 if( rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode() )
1293 *rPam.GetMark() = *rPam.GetPoint();
1294 rDoc.getIDocumentContentOperations().DelFullPara( rPam );
1296 else
1297 // FIXME: this ends up calling DeleteBookmarks() on the entire rPam which deletes too many!
1298 rDoc.getIDocumentContentOperations().DeleteAndJoin(rPam, m_DeleteFlags);
1301 void SwUndoDelete::RepeatImpl(::sw::RepeatContext & rContext)
1303 // this action does not seem idempotent,
1304 // so make sure it is only executed once on repeat
1305 if (rContext.m_bDeleteRepeated)
1306 return;
1308 SwPaM & rPam = rContext.GetRepeatPaM();
1309 SwDoc& rDoc = rPam.GetDoc();
1310 ::sw::GroupUndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
1311 if( !rPam.HasMark() )
1313 rPam.SetMark();
1314 rPam.Move( fnMoveForward, GoInContent );
1316 if( m_bDelFullPara )
1317 rDoc.getIDocumentContentOperations().DelFullPara( rPam );
1318 else
1319 rDoc.getIDocumentContentOperations().DeleteAndJoin( rPam );
1320 rContext.m_bDeleteRepeated = true;
1323 void SwUndoDelete::SetTableName(const OUString & rName)
1325 m_sTableName = rName;
1328 void SwUndoDelete::dumpAsXml(xmlTextWriterPtr pWriter) const
1330 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwUndoDelete"));
1331 (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
1332 SwUndo::dumpAsXml(pWriter);
1333 SwUndoSaveContent::dumpAsXml(pWriter);
1334 (void)xmlTextWriterEndElement(pWriter);
1337 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */