/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
std::vector<SwTextFrame*> frames;
SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pFirstMergedDeletedTextNode); for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
{ if (pFrame->getRootFrame()->HasMergedParas())
{
assert(pFrame->GetMergedPara());
assert(pFrame->GetMergedPara()->pFirstNode == pFirstMergedDeletedTextNode);
assert(pNextNode->GetIndex() <= pFrame->GetMergedPara()->pLastNode->GetIndex());
frames.push_back(pFrame);
}
} for (SwTextFrame *const pFrame : frames)
{ // sw_redlinehide: don't need FrameMode::Existing here // because everything from pNextNode onwards is already // correctly hidden
pFrame->RegisterToNode(*pNextNode, true);
}
}
// SwUndoDelete has to perform a deletion and to record anything that is needed // to restore the situation before the deletion. Unfortunately a part of the // deletion will be done after calling this Ctor, this has to be kept in mind! // In this Ctor only the complete paragraphs will be deleted, the joining of // the first and last paragraph of the selection will be handled outside this // function. // Here are the main steps of the function: // 1. Deletion/recording of content indices of the selection: footnotes, fly // frames and bookmarks // Step 1 could shift all nodes by deletion of footnotes => nNdDiff will be set. // 2. If the paragraph where the selection ends, is the last content of a // section so that this section becomes empty when the paragraphs will be // joined we have to do some smart actions ;-) The paragraph will be moved // outside the section and replaced by a dummy text node, the complete // section will be deleted in step 3. The difference between replacement // dummy and original is nReplacementDummy. // 3. Moving complete selected nodes into the UndoArray. Before this happens the // selection has to be extended if there are sections which would become // empty otherwise. BTW: sections will be moved into the UndoArray if they // are complete part of the selection. Sections starting or ending outside // of the selection will not be removed from the DocNodeArray even they got // a "dummy"-copy in the UndoArray. // 4. We have to anticipate the joining of the two paragraphs if the start // paragraph is inside a section and the end paragraph not. Then we have to // move the paragraph into this section and to record this in nSectDiff.
SwUndoDelete::SwUndoDelete(
SwPaM& rPam,
SwDeleteFlags const flags, bool bFullPara, bool bCalledByTableCpy )
: SwUndo(SwUndoId::DELETE, rPam.GetDoc()),
SwUndRng( rPam ),
m_nNode(0),
m_nNdDiff(0),
m_nSectDiff(0),
m_nReplaceDummy(0),
m_nSetPos(0),
m_bGroup( false ),
m_bBackSp( false ),
m_bJoinNext( false ),
m_bTableDelLastNd( false ), // bFullPara is set e.g. if an empty paragraph before a table is deleted
m_bDelFullPara( bFullPara ),
m_bResetPgDesc( false ),
m_bResetPgBrk( false ),
m_bFromTableCopy( bCalledByTableCpy )
, m_DeleteFlags(flags)
{
assert(!m_bDelFullPara || !(m_DeleteFlags & SwDeleteFlags::ArtificialSelection));
bool bMoveNds = *pStart != *pEnd // any area still existent?
&& ( SaveContent( pStart, pEnd, pSttTextNd, pEndTextNd ) || m_bFromTableCopy );
if( pSttTextNd && pEndTextNd && pSttTextNd != pEndTextNd )
{ // two different TextNodes, thus save also the TextFormatCollection
m_pHistory->AddColl(pSttTextNd->GetTextColl(), pStart->GetNodeIndex(), SwNodeType::Text);
m_pHistory->AddColl(pEndTextNd->GetTextColl(), pEnd->GetNodeIndex(), SwNodeType::Text);
if( !m_bJoinNext ) // Selection from bottom to top
{ // When using JoinPrev() all AUTO-PageBreak's will be copied // correctly. To restore them with UNDO, Auto-PageBreak of the // EndNode needs to be reset. Same for PageDesc and ColBreak. if( pEndTextNd->HasSwAttrSet() )
{
SwRegHistory aRegHist( *pEndTextNd, m_pHistory.get() ); if( SfxItemState::SET == pEndTextNd->GetpSwAttrSet()->GetItemState(
RES_BREAK, false ) )
pEndTextNd->ResetAttr( RES_BREAK ); if( pEndTextNd->HasSwAttrSet() &&
SfxItemState::SET == pEndTextNd->GetpSwAttrSet()->GetItemState(
RES_PAGEDESC, false ) )
pEndTextNd->ResetAttr( RES_PAGEDESC );
}
}
}
// Move now also the PaM. The SPoint is at the beginning of a SSelection. if( pEnd == rPam.GetPoint() && ( !bFullPara || pSttTextNd || pEndTextNd ) )
rPam.Exchange();
if( !pSttTextNd && !pEndTextNd )
rPam.GetPoint()->Adjust(SwNodeOffset(-1));
rPam.DeleteMark(); // the SPoint is in the selection
if( pSttTextNd && pEndTextNd )
{ //Step 4: Moving around sections
m_nSectDiff = aRg.aEnd.GetIndex() - aRg.aStart.GetIndex(); // nSect is the number of sections which starts(ends) between start // and end node of the selection. The "loser" paragraph has to be // moved into the section(s) of the "winner" paragraph if( m_nSectDiff )
{ if( m_bJoinNext )
{
SwNodeRange aMvRg( *pEndTextNd, SwNodeOffset(0), *pEndTextNd, SwNodeOffset(1) );
rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aStart.GetNode() );
} else
{
SwNodeRange aMvRg( *pSttTextNd, SwNodeOffset(0), *pSttTextNd, SwNodeOffset(1) );
rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aEnd.GetNode() );
}
}
} if( m_nSectDiff || m_nReplaceDummy )
{
SwNodeOffset nIndex; if (m_bJoinNext)
{
assert(pEndTextNd);
nIndex = pEndTextNd->GetIndex();
} else
{
assert(pSttTextNd);
nIndex = pSttTextNd->GetIndex();
}
lcl_MakeAutoFrames( *rDoc.GetSpzFrameFormats(), nIndex);
}
} else
m_nNode = SwNodeOffset(0); // moved no node -> no difference at the end
// Are there any Nodes that got deleted before that (FootNotes // have ContentNodes)? if( !pSttTextNd && !pEndTextNd )
{
m_nNdDiff = m_nSttNode - rPam.GetPoint()->GetNodeIndex() - (bFullPara ? 0 : 1);
rPam.Move( fnMoveForward, GoInNode );
} else
{
m_nNdDiff = m_nSttNode; if( m_nSectDiff && m_bBackSp )
m_nNdDiff += m_nSectDiff;
m_nNdDiff -= rPam.GetPoint()->GetNodeIndex();
}
// is a history necessary here at all? if( m_pHistory && !m_pHistory->Count() )
m_pHistory.reset();
}
bool SwUndoDelete::SaveContent( const SwPosition* pStart, const SwPosition* pEnd,
SwTextNode* pSttTextNd, SwTextNode* pEndTextNd )
{
SwNodeOffset nNdIdx = pStart->GetNodeIndex(); // 1 - copy start in Start-String if( pSttTextNd )
{ bool bOneNode = m_nSttNode == m_nEndNode;
SwRegHistory aRHst( *pSttTextNd, m_pHistory.get() ); // always save all text atttibutes because of possibly overlapping // areas of on/off
m_pHistory->CopyAttr( pSttTextNd->GetpSwpHints(), nNdIdx,
0, pSttTextNd->GetText().getLength(), true ); if( !bOneNode && pSttTextNd->HasSwAttrSet() )
m_pHistory->CopyFormatAttr( *pSttTextNd->GetpSwAttrSet(), nNdIdx );
// the length might have changed (!!Fields!!)
sal_Int32 nLen = (bOneNode
? pEnd->GetContentIndex()
: pSttTextNd->GetText().getLength())
- pStart->GetContentIndex();
// delete now also the text (all attribute changes are added to // UNDO history)
m_aSttStr = pSttTextNd->GetText().copy(m_nSttContent, nLen);
pSttTextNd->EraseText( *pStart, nLen ); if( pSttTextNd->GetpSwpHints() )
pSttTextNd->GetpSwpHints()->DeRegister();
// METADATA: store bool emptied( !m_aSttStr->isEmpty() && !pSttTextNd->Len() ); if (!bOneNode || emptied) // merging may overwrite xmlids...
{
m_pMetadataUndoStart = emptied
? pSttTextNd->CreateUndoForDelete()
: pSttTextNd->CreateUndo();
}
if( bOneNode ) returnfalse; // stop moving more nodes
}
// always save all text atttibutes because of possibly overlapping // areas of on/off
m_pHistory->CopyAttr( pEndTextNd->GetpSwpHints(), nNdIdx, 0,
pEndTextNd->GetText().getLength(), true );
// delete now also the text (all attribute changes are added to // UNDO history)
m_aEndStr = pEndTextNd->GetText().copy( 0, pEnd->GetContentIndex() );
pEndTextNd->EraseText( aEndIdx, pEnd->GetContentIndex() ); if( pEndTextNd->GetpSwpHints() )
pEndTextNd->GetpSwpHints()->DeRegister();
// METADATA: store bool emptied = !m_aEndStr->isEmpty() && !pEndTextNd->Len();
// tdf#132725 - if at-char/at-para flys would be deleted, don't group! // DelContentIndex() would be called at the wrong time here, the indexes // in the stored SwHistoryTextFlyCnt would be wrong when Undo is invoked if (IsFlySelectedByCursor(rDoc, *pStart, *pEnd))
{ returnfalse;
}
if ((m_DeleteFlags & SwDeleteFlags::ArtificialSelection) && m_pHistory)
{
IDocumentMarkAccess const& rIDMA(*rDoc.getIDocumentMarkAccess()); for (auto i = m_pHistory->Count(); 0 < i; )
{
--i;
SwHistoryHint const*const pHistory((*m_pHistory)[i]); if (pHistory->Which() == HSTRY_BOOKMARK)
{
SwHistoryBookmark const*const pHistoryBM( static_cast<SwHistoryBookmark const*>(pHistory)); autoconst ppMark(rIDMA.findMark(pHistoryBM->GetName())); if (ppMark != rIDMA.getAllMarksEnd()
&& (m_bBackSp
? ((**ppMark).GetMarkPos() == *pStart)
: ((**ppMark).IsExpanded()
&& (**ppMark).GetOtherMarkPos() == *pEnd)))
{ // prevent grouping that would delete this mark on Redo() returnfalse;
}
}
}
}
// Both 'deletes' can be consolidated, so 'move' the related character if( m_bBackSp )
m_nSttContent--; // BackSpace: add char to array! else
{
m_nEndContent++; // Delete: attach char at the end
nUChrPos++;
}
m_aSttStr = m_aSttStr->replaceAt( nUChrPos, 0, rtl::OUStringChar(cDelChar) );
pDelTextNd->EraseText( *pStart, 1 );
m_bGroup = true; returntrue;
}
SwUndoDelete::~SwUndoDelete()
{ if( m_oMvStt ) // Delete also the selection from UndoNodes array
{ // Insert saves content in IconSection
m_oMvStt->GetNode().GetNodes().Delete( *m_oMvStt, m_nNode );
m_oMvStt.reset();
}
m_pRedlSaveData.reset();
}
for ( sal_uInt16 n = 0; n < rHistory.Count(); n++)
{
OUString aDescr = rHistory[n]->GetDescription();
if (!aDescr.isEmpty())
{
aRewriter.AddRule(UndoArg2, aDescr);
bDone = true; break;
}
}
if (! bDone)
{
aRewriter.AddRule(UndoArg2, SwResId(STR_FIELD));
}
return aRewriter;
}
staticbool lcl_IsSpecialCharacter(sal_Unicode nChar)
{ switch (nChar)
{ case CH_TXTATR_BREAKWORD: case CH_TXTATR_INWORD: case CH_TXTATR_TAB: case CH_TXTATR_NEWLINE: case CH_TXT_ATR_INPUTFIELDSTART: case CH_TXT_ATR_INPUTFIELDEND: case CH_TXT_ATR_FORMELEMENT: case CH_TXT_ATR_FIELDSTART: case CH_TXT_ATR_FIELDSEP: case CH_TXT_ATR_FIELDEND: returntrue;
auto nCount = nEnd - nStart; if (nCount > 0)
{
sal_Unicode cLast = rStr[nEnd - 1]; if (lcl_IsSpecialCharacter(cLast))
{ switch(cLast)
{ case CH_TXTATR_TAB:
aResult = SwResId(STR_UNDO_TABS, nCount);
break; case CH_TXTATR_NEWLINE:
aResult = SwResId(STR_UNDO_NLS, nCount);
break;
case CH_TXTATR_INWORD: case CH_TXTATR_BREAKWORD:
aResult = SwRewriter::GetPlaceHolder(UndoArg2); break;
case CH_TXT_ATR_INPUTFIELDSTART: case CH_TXT_ATR_INPUTFIELDEND: case CH_TXT_ATR_FORMELEMENT: case CH_TXT_ATR_FIELDSTART: case CH_TXT_ATR_FIELDSEP: case CH_TXT_ATR_FIELDEND: break; // nothing?
{ // code block so that SwPosition is detached when deleting a Node
SwPosition aPos( aIdx ); if( !m_bDelFullPara )
{
assert(!m_bTableDelLastNd || pInsNd->IsTextNode()); if( pInsNd->IsTableNode() )
{
pInsNd = rDoc.GetNodes().MakeTextNode( aIdx.GetNode(),
rDoc.GetDfltTextFormatColl() );
--aIdx;
aPos.Assign( *pInsNd->GetContentNode(), m_nSttContent );
} else
{ if( pInsNd->IsContentNode() )
aPos.SetContent( m_nSttContent ); if( !m_bTableDelLastNd )
pInsNd = nullptr; // do not delete Node!
}
} else
pInsNd = nullptr; // do not delete Node!
bool bNodeMove = SwNodeOffset(0) != m_nNode;
if( m_aEndStr )
{ // discard attributes since they all saved!
SwTextNode * pTextNd; if (!m_bDelFullPara && aPos.GetNode().IsSectionNode())
{ // tdf#134250 section node wasn't deleted; but aPos must point to it in bNodeMove case below
assert(m_nSttContent == 0);
assert(!m_aSttStr);
pTextNd = rDoc.GetNodes()[aPos.GetNodeIndex() + 1]->GetTextNode();
} else
{
pTextNd = aPos.GetNode().GetTextNode();
}
// SectionNode mode and selection from top to bottom: // -> in StartNode is still the rest of the Join => delete
aPos.SetContent( m_nSttContent );
pTextNd->SetInSwUndo(true);
OUString const ins( pTextNd->InsertText(*m_aSttStr, aPos,
SwInsertFlags::NOHINTEXPAND) );
pTextNd->SetInSwUndo(false);
assert(ins.getLength() == m_aSttStr->getLength()); // must succeed
(void) ins; // METADATA: restore
pTextNd->RestoreMetadata(m_pMetadataUndoStart);
}
}
if( m_pHistory )
{
m_pHistory->TmpRollback(rDoc, m_nSetPos, false); if( m_nSetPos ) // there were Footnodes/FlyFrames
{ // are there others than these ones? if( m_nSetPos < m_pHistory->Count() )
{ // if so save the attributes of the others
SwHistory aHstr;
aHstr.Move( 0, m_pHistory.get(), m_nSetPos );
m_pHistory->Rollback(rDoc);
m_pHistory->Move( 0, &aHstr );
} else
{
m_pHistory->Rollback(rDoc);
m_pHistory.reset();
}
}
}
SwNodeOffset delFullParaEndNode(m_nEndNode); if (m_bDelFullPara && m_pRedlSaveData)
{
SwTextNode * pFirstMergedDeletedTextNode(nullptr);
SwTextNode *const pNextNode = FindFirstAndNextNode(rDoc, *this,
*m_pRedlSaveData, pFirstMergedDeletedTextNode); if (pNextNode)
{ bool bNonMerged(false);
std::vector<SwTextFrame*> frames;
SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pNextNode); for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
{ if (pFrame->getRootFrame()->HasMergedParas())
{
frames.push_back(pFrame);
} else
{
bNonMerged = true;
}
} for (SwTextFrame *const pFrame : frames)
{ // could either destroy the text frames, or move them... // destroying them would have the advantage that we don't // need special code to *exclude* pFirstMergedDeletedTextNode // from MakeFrames for the layouts in Hide mode but not // layouts in Show mode ... // ... except that MakeFrames won't create them then :(
pFrame->RegisterToNode(*pFirstMergedDeletedTextNode);
assert(pFrame->GetMergedPara());
assert(!bNonMerged); // delFullParaEndNode is such an awful hack
(void) bNonMerged;
delFullParaEndNode = pFirstMergedDeletedTextNode->GetIndex();
}
}
} elseif (m_aSttStr && (!m_bFromTableCopy || SwNodeOffset(0) != m_nNode))
{ // only now do we have redlines in the document again; fix up the split // frames
SwTextNode *const pStartNode(aIdx.GetNodes()[m_nSttNode]->GetTextNode());
assert(pStartNode);
sw::RecreateStartTextFrames(*pStartNode);
}
// create frames after SetSaveData has recreated redlines if (SwNodeOffset(0) != m_nNode)
{ // tdf#136453 only if section nodes at the start if (m_bBackSp && m_nReplaceDummy != SwNodeOffset(0))
{ // tdf#134252 *first* create outer section frames // note: text node m_nSttNode currently has frame with an upper; // there's a hack in InsertCnt_() to move it below new section frame
SwNode& start(*rDoc.GetNodes()[m_nSttNode - m_nReplaceDummy]);
SwNode& end(*rDoc.GetNodes()[m_nSttNode]); // exclude m_nSttNode
::MakeFrames(rDoc, start, end);
} // tdf#121031 if the start node is a text node, it already has a frame; // if it's a table, it does not // tdf#109376 exception: end on non-text-node -> start node was inserted
assert(!m_bDelFullPara || (m_nSectDiff == SwNodeOffset(0)));
SwNode& start(*rDoc.GetNodes()[m_nSttNode +
((m_bDelFullPara || !rDoc.GetNodes()[m_nSttNode]->IsTextNode() || pInsNd)
? 0 : 1)]); // tdf#158740 fix crash by checking the end node's index // I don't know why m_nEndNode is larger than the size of the node // array, but adjusting m_nEndNode to the last element in the node // array stops the crashing.
SwNodeOffset nCount(rDoc.GetNodes().Count()); if (nCount > SwNodeOffset(0))
{ if (m_nEndNode > nCount - 1)
m_nEndNode = nCount - 1;
// don't include end node in the range: it may have been merged already // by the start node, or it may be merged by one of the moved nodes, // but if it isn't merged, its current frame(s) should be good...
SwNode& end(*rDoc.GetNodes()[ m_bDelFullPara
? delFullParaEndNode // tdf#147310 SwDoc::DeleteRowCol() may delete whole table - end must be node following table!
: (m_nEndNode + (rDoc.GetNodes()[m_nSttNode]->IsTableNode() && rDoc.GetNodes()[m_nEndNode]->IsEndNode() ? 1 : 0))]);
::MakeFrames(rDoc, start, end);
}
}
if (pMovedNode)
{ // probably better do this after creating all frames
lcl_MakeAutoFrames(*rDoc.GetSpzFrameFormats(), pMovedNode->GetIndex());
}
// tdf#134021 only after MakeFrames(), because it may be the only node // that has layout frames if (pInsNd && m_bTableDelLastNd)
{
assert(&aIdx.GetNode() == pInsNd);
SwPaM tmp(aIdx, aIdx);
rDoc.getIDocumentContentOperations().DelFullPara(tmp);
}
if( m_pRedlSaveData )
{ constbool bSuccess = FillSaveData(rPam, *m_pRedlSaveData);
OSL_ENSURE(bSuccess, "SwUndoDelete::Redo: used to have redline data, but now none?"); if (!bSuccess)
{
m_pRedlSaveData.reset();
}
}
if( !m_bDelFullPara )
{ // tdf#128739 correct cursors but do not delete bookmarks yet
::PaMCorrAbs(rPam, *rPam.End());
SetPaM(rPam);
if( !m_bJoinNext ) // then restore selection from bottom to top
rPam.Exchange();
}
if( m_pHistory ) // are the attributes saved?
{
m_pHistory->SetTmpEnd( m_pHistory->Count() );
SwHistory aHstr;
aHstr.Move( 0, m_pHistory.get() );
if( const SvxFormatBreakItem* pItem = pTableFormat->GetItemIfSet( RES_BREAK, false ) )
pNextNd->SetAttr( *pItem );
}
pTableNd->DelFrames();
} elseif (*rPam.GetMark() == *rPam.GetPoint())
{ // paragraph with only footnote or as-char fly, delete that // => DelContentIndex has already deleted it! nothing to do here
assert(m_nEndNode == m_nSttNode); return;
}
// avoid asserts from ~SwContentIndexReg for deleted nodes
SwPaM aTmp(*rPam.End()); if (!aTmp.Move(fnMoveForward, GoInNode))
{
*aTmp.GetPoint() = *rPam.Start();
aTmp.Move(fnMoveBackward, GoInNode);
} // coverity[copy_paste_error : FALSE] : GetNode() is intentional on both branches
assert(aTmp.GetPoint()->GetNode() != rPam.GetPoint()->GetNode()
&& aTmp.GetPoint()->GetNode() != rPam.GetMark()->GetNode());
::PaMCorrAbs(rPam, *aTmp.GetPoint());
rPam.DeleteMark();
rDoc.GetNodes().Delete( rSttNd, m_nEndNode - m_nSttNode );
} elseif( m_bDelFullPara )
{
assert(!"dead code"); // The Pam was incremented by one at Point (== end) to provide space // for UNDO. This now needs to be reverted!
rPam.End()->Adjust(SwNodeOffset(-1)); if( rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode() )
*rPam.GetMark() = *rPam.GetPoint();
rDoc.getIDocumentContentOperations().DelFullPara( rPam );
} else
rDoc.getIDocumentContentOperations().DeleteAndJoin(rPam, m_DeleteFlags);
}
void SwUndoDelete::RepeatImpl(::sw::RepeatContext & rContext)
{ // this action does not seem idempotent, // so make sure it is only executed once on repeat if (rContext.m_bDeleteRepeated) return;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.