/* -*- 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 .
*/
#include <UndoDelete.hxx>
#include <hintids.hxx>
#include <unotools/charclass.hxx>
#include <editeng/formatbreakitem.hxx>
#include <fmtpdsc.hxx>
#include <frmfmt.hxx>
#include <fmtanchr.hxx>
#include <doc.hxx>
#include <UndoManager.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <IDocumentStylePoolAccess.hxx>
#include <swtable.hxx>
#include <swundo.hxx>
#include <pam.hxx>
#include <ndtxt.hxx>
#include <UndoCore.hxx>
#include <rolbck.hxx>
#include <poolfmt.hxx>
#include <mvsave.hxx>
#include <redline.hxx>
#include <docary.hxx>
#include <sfx2/app.hxx>
#include <fldbas.hxx>
#include <fmtfld.hxx>
#include <strings.hrc>
#include <vector>
// DELETE
/* lcl_MakeAutoFrames has to call MakeFrames for objects bounded "AtChar"
( == AUTO ), if the anchor frame has be moved via MoveNodes(..) and
DelFrames(..)
*/
static void lcl_MakeAutoFrames( const SwFrameFormats& rSpzArr, sal_uLong nMovedIndex )
{
if( !rSpzArr.empty() )
{
for( size_t n = 0; n < rSpzArr.size(); ++n )
{
SwFrameFormat * pFormat = rSpzArr[n];
const SwFormatAnchor* pAnchor = &pFormat->GetAnchor();
if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR)
{
const SwPosition* pAPos = pAnchor->GetContentAnchor();
if( pAPos && nMovedIndex == pAPos->nNode.GetIndex() )
pFormat->MakeFrames();
}
}
}
}
// 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,
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 )
{
bCacheComment = false;
SwDoc * pDoc = rPam.GetDoc();
if( !pDoc->getIDocumentRedlineAccess().IsIgnoreRedline() && !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() )
{
m_pRedlSaveData.reset(new SwRedlineSaveDatas);
if( !FillSaveData( rPam, *m_pRedlSaveData ))
{
m_pRedlSaveData.reset();
}
}
if( !pHistory )
pHistory.reset( new SwHistory );
// delete all footnotes for now
const SwPosition *pStt = rPam.Start(),
*pEnd = rPam.GetPoint() == pStt
? rPam.GetMark()
: rPam.GetPoint();
// Step 1. deletion/record of content indices
if( m_bDelFullPara )
{
OSL_ENSURE( rPam.HasMark(), "PaM without Mark" );
DelContentIndex( *rPam.GetMark(), *rPam.GetPoint(),
DelContentType(DelContentType::AllMask | DelContentType::CheckNoCntnt) );
::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo());
DelBookmarks(pStt->nNode, pEnd->nNode);
}
else
{
DelContentIndex( *rPam.GetMark(), *rPam.GetPoint() );
::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo());
if (nEndNode - nSttNode > 1) // check for fully selected nodes
{
SwNodeIndex const start(pStt->nNode, +1);
DelBookmarks(start, pEnd->nNode);
}
}
m_nSetPos = pHistory ? pHistory->Count() : 0;
// Is already anything deleted?
m_nNdDiff = nSttNode - pStt->nNode.GetIndex();
m_bJoinNext = !bFullPara && pEnd == rPam.GetPoint();
m_bBackSp = !bFullPara && !m_bJoinNext;
SwTextNode *pSttTextNd = nullptr, *pEndTextNd = nullptr;
if( !bFullPara )
{
pSttTextNd = pStt->nNode.GetNode().GetTextNode();
pEndTextNd = nSttNode == nEndNode
? pSttTextNd
: pEnd->nNode.GetNode().GetTextNode();
}
bool bMoveNds = *pStt != *pEnd // any area still existent?
&& ( SaveContent( pStt, pEnd, pSttTextNd, pEndTextNd ) || m_bFromTableCopy );
if( pSttTextNd && pEndTextNd && pSttTextNd != pEndTextNd )
{
// two different TextNodes, thus save also the TextFormatCollection
pHistory->Add( pSttTextNd->GetTextColl(),pStt->nNode.GetIndex(), SwNodeType::Text );
pHistory->Add( pEndTextNd->GetTextColl(),pEnd->nNode.GetIndex(), 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, 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()->nNode;
rPam.DeleteMark(); // the SPoint is in the selection
if( !pEndTextNd )
nEndContent = 0;
if( !pSttTextNd )
nSttContent = 0;
if( bMoveNds ) // Do Nodes exist that need to be moved?
{
SwNodes& rNds = pDoc->GetUndoManager().GetUndoNodes();
SwNodes& rDocNds = pDoc->GetNodes();
SwNodeRange aRg( rDocNds, nSttNode - m_nNdDiff,
rDocNds, nEndNode - m_nNdDiff );
if( !bFullPara && !pEndTextNd &&
&aRg.aEnd.GetNode() != &pDoc->GetNodes().GetEndOfContent() )
{
SwNode* pNode = aRg.aEnd.GetNode().StartOfSectionNode();
if( pNode->GetIndex() >= nSttNode - m_nNdDiff )
++aRg.aEnd; // Deletion of a complete table
}
SwNode* pTmpNd;
// Step 2: Expand selection if necessary
if( m_bJoinNext || bFullPara )
{
// If all content of a section will be moved into Undo, the section
// itself should be moved completely.
while( aRg.aEnd.GetIndex() + 2 < rDocNds.Count() &&
( (pTmpNd = rDocNds[ aRg.aEnd.GetIndex()+1 ])->IsEndNode() &&
pTmpNd->StartOfSectionNode()->IsSectionNode() &&
pTmpNd->StartOfSectionNode()->GetIndex() >= aRg.aStart.GetIndex() ) )
++aRg.aEnd;
m_nReplaceDummy = aRg.aEnd.GetIndex() + m_nNdDiff - nEndNode;
if( m_nReplaceDummy )
{ // The selection has been expanded, because
++aRg.aEnd;
if( pEndTextNd )
{
// The end text node has to leave the (expanded) selection
// The dummy is needed because MoveNodes deletes empty
// sections
++m_nReplaceDummy;
SwNodeRange aMvRg( *pEndTextNd, 0, *pEndTextNd, 1 );
SwPosition aSplitPos( *pEndTextNd );
::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo());
pDoc->getIDocumentContentOperations().SplitNode( aSplitPos, false );
rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aEnd );
--aRg.aEnd;
}
else
m_nReplaceDummy = 0;
}
}
if( m_bBackSp || bFullPara )
{
// See above, the selection has to be expanded if there are "nearly
// empty" sections and a replacement dummy has to be set if needed.
while( 1 < aRg.aStart.GetIndex() &&
( (pTmpNd = rDocNds[ aRg.aStart.GetIndex()-1 ])->IsSectionNode() &&
pTmpNd->EndOfSectionIndex() < aRg.aEnd.GetIndex() ) )
--aRg.aStart;
if( pSttTextNd )
{
m_nReplaceDummy = nSttNode - m_nNdDiff - aRg.aStart.GetIndex();
if( m_nReplaceDummy )
{
SwNodeRange aMvRg( *pSttTextNd, 0, *pSttTextNd, 1 );
SwPosition aSplitPos( *pSttTextNd );
::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo());
pDoc->getIDocumentContentOperations().SplitNode( aSplitPos, false );
rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aStart );
--aRg.aStart;
}
}
}
if( m_bFromTableCopy )
{
if( !pEndTextNd )
{
if( pSttTextNd )
++aRg.aStart;
else if( !bFullPara && !aRg.aEnd.GetNode().IsContentNode() )
--aRg.aEnd;
}
}
else if (pSttTextNd && (pEndTextNd || pSttTextNd->GetText().getLength()))
++aRg.aStart;
// Step 3: Moving into UndoArray...
m_nNode = rNds.GetEndOfContent().GetIndex();
rDocNds.MoveNodes( aRg, rNds, SwNodeIndex( rNds.GetEndOfContent() ));
m_pMvStt.reset( new SwNodeIndex( rNds, m_nNode ) );
// remember difference!
m_nNode = rNds.GetEndOfContent().GetIndex() - m_nNode;
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, 0, *pEndTextNd, 1 );
rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aStart );
}
else
{
SwNodeRange aMvRg( *pSttTextNd, 0, *pSttTextNd, 1 );
rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aEnd );
}
}
}
if( m_nSectDiff || m_nReplaceDummy )
lcl_MakeAutoFrames( *pDoc->GetSpzFrameFormats(),
m_bJoinNext ? pEndTextNd->GetIndex() : pSttTextNd->GetIndex() );
}
else
m_nNode = 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 = nSttNode - rPam.GetPoint()->nNode.GetIndex() - (bFullPara ? 0 : 1);
rPam.Move( fnMoveForward, GoInNode );
}
else
{
m_nNdDiff = nSttNode;
if( m_nSectDiff && m_bBackSp )
m_nNdDiff += m_nSectDiff;
m_nNdDiff -= rPam.GetPoint()->nNode.GetIndex();
}
if( !rPam.GetNode().IsContentNode() )
rPam.GetPoint()->nContent.Assign( nullptr, 0 );
// is a history necessary here at all?
if( pHistory && !pHistory->Count() )
pHistory.reset();
}
bool SwUndoDelete::SaveContent( const SwPosition* pStt, const SwPosition* pEnd,
SwTextNode* pSttTextNd, SwTextNode* pEndTextNd )
{
sal_uLong nNdIdx = pStt->nNode.GetIndex();
// 1 - copy start in Start-String
if( pSttTextNd )
{
bool bOneNode = nSttNode == nEndNode;
SwRegHistory aRHst( *pSttTextNd, pHistory.get() );
// always save all text atttibutes because of possibly overlapping
// areas of on/off
pHistory->CopyAttr( pSttTextNd->GetpSwpHints(), nNdIdx,
0, pSttTextNd->GetText().getLength(), true );
if( !bOneNode && pSttTextNd->HasSwAttrSet() )
pHistory->CopyFormatAttr( *pSttTextNd->GetpSwAttrSet(), nNdIdx );
// the length might have changed (!!Fields!!)
sal_Int32 nLen = (bOneNode
? pEnd->nContent.GetIndex()
: pSttTextNd->GetText().getLength())
- pStt->nContent.GetIndex();
// delete now also the text (all attribute changes are added to
// UNDO history)
m_aSttStr = pSttTextNd->GetText().copy(nSttContent, nLen);
pSttTextNd->EraseText( pStt->nContent, 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 )
return false; // stop moving more nodes
}
// 2 - copy end into End-String
if( pEndTextNd )
{
SwIndex aEndIdx( pEndTextNd );
nNdIdx = pEnd->nNode.GetIndex();
SwRegHistory aRHst( *pEndTextNd, pHistory.get() );
// always save all text atttibutes because of possibly overlapping
// areas of on/off
pHistory->CopyAttr( pEndTextNd->GetpSwpHints(), nNdIdx, 0,
pEndTextNd->GetText().getLength(), true );
if( pEndTextNd->HasSwAttrSet() )
pHistory->CopyFormatAttr( *pEndTextNd->GetpSwAttrSet(), nNdIdx );
// delete now also the text (all attribute changes are added to
// UNDO history)
m_aEndStr = pEndTextNd->GetText().copy( 0, pEnd->nContent.GetIndex() );
pEndTextNd->EraseText( aEndIdx, pEnd->nContent.GetIndex() );
if( pEndTextNd->GetpSwpHints() )
pEndTextNd->GetpSwpHints()->DeRegister();
// METADATA: store
bool emptied = !m_aEndStr->isEmpty() && !pEndTextNd->Len();
m_pMetadataUndoEnd = emptied
? pEndTextNd->CreateUndoForDelete()
: pEndTextNd->CreateUndo();
}
// if there are only two Nodes than we're done
if( ( pSttTextNd || pEndTextNd ) && nSttNode + 1 == nEndNode )
return false; // do not move any Node
return true; // move Nodes lying in between
}
bool SwUndoDelete::CanGrouping( SwDoc* pDoc, const SwPaM& rDelPam )
{
// Is Undo greater than one Node (that is Start and EndString)?
if( !m_aSttStr || m_aSttStr->isEmpty() || m_aEndStr )
return false;
// only the deletion of single char's can be condensed
if( nSttNode != nEndNode || ( !m_bGroup && nSttContent+1 != nEndContent ))
return false;
const SwPosition *pStt = rDelPam.Start(),
*pEnd = rDelPam.GetPoint() == pStt
? rDelPam.GetMark()
: rDelPam.GetPoint();
if( pStt->nNode != pEnd->nNode ||
pStt->nContent.GetIndex()+1 != pEnd->nContent.GetIndex() ||
pEnd->nNode != nSttNode )
return false;
// Distinguish between BackSpace and Delete because the Undo array needs to
// be constructed differently!
if( pEnd->nContent == nSttContent )
{
if( m_bGroup && !m_bBackSp ) return false;
m_bBackSp = true;
}
else if( pStt->nContent == nSttContent )
{
if( m_bGroup && m_bBackSp ) return false;
m_bBackSp = false;
}
else
return false;
// are both Nodes (Node/Undo array) TextNodes at all?
SwTextNode * pDelTextNd = pStt->nNode.GetNode().GetTextNode();
if( !pDelTextNd ) return false;
sal_Int32 nUChrPos = m_bBackSp ? 0 : m_aSttStr->getLength()-1;
sal_Unicode cDelChar = pDelTextNd->GetText()[ pStt->nContent.GetIndex() ];
CharClass& rCC = GetAppCharClass();
if( ( CH_TXTATR_BREAKWORD == cDelChar || CH_TXTATR_INWORD == cDelChar ) ||
rCC.isLetterNumeric( OUString( cDelChar ), 0 ) !=
rCC.isLetterNumeric( *m_aSttStr, nUChrPos ) )
return false;
{
SwRedlineSaveDatas aTmpSav;
const bool bSaved = FillSaveData( rDelPam, aTmpSav, false );
bool bOk = ( !m_pRedlSaveData && !bSaved ) ||
( m_pRedlSaveData && bSaved &&
SwUndo::CanRedlineGroup( *m_pRedlSaveData, aTmpSav, m_bBackSp ));
// aTmpSav.DeleteAndDestroyAll();
if( !bOk )
return false;
pDoc->getIDocumentRedlineAccess().DeleteRedline( rDelPam, false, USHRT_MAX );
}
// Both 'deletes' can be consolidated, so 'move' the related character
if( m_bBackSp )
nSttContent--; // BackSpace: add char to array!
else
{
nEndContent++; // Delete: attach char at the end
nUChrPos++;
}
m_aSttStr = m_aSttStr->replaceAt( nUChrPos, 0, OUString(cDelChar) );
pDelTextNd->EraseText( pStt->nContent, 1 );
m_bGroup = true;
return true;
}
SwUndoDelete::~SwUndoDelete()
{
if( m_pMvStt ) // Delete also the selection from UndoNodes array
{
// Insert saves content in IconSection
m_pMvStt->GetNode().GetNodes().Delete( *m_pMvStt, m_nNode );
m_pMvStt.reset();
}
m_pRedlSaveData.reset();
}
static SwRewriter lcl_RewriterFromHistory(SwHistory & rHistory)
{
SwRewriter aRewriter;
bool bDone = false;
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;
}
static bool 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_FIELDEND:
return true;
default:
break;
}
return false;
}
static OUString lcl_DenotedPortion(const OUString& rStr, sal_Int32 nStart, sal_Int32 nEnd)
{
OUString aResult;
if (nEnd - nStart > 0)
{
sal_Unicode cLast = rStr[nEnd - 1];
if (lcl_IsSpecialCharacter(cLast))
{
switch(cLast)
{
case CH_TXTATR_TAB:
aResult = SwResId(STR_UNDO_TABS);
break;
case CH_TXTATR_NEWLINE:
aResult = SwResId(STR_UNDO_NLS);
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_FIELDEND:
break; // nothing?
default:
assert(!"unexpected special character");
break;
}
SwRewriter aRewriter;
aRewriter.AddRule(UndoArg1,
OUString::number(nEnd - nStart));
aResult = aRewriter.Apply(aResult);
}
else
{
aResult = SwResId(STR_START_QUOTE);
aResult += rStr.copy(nStart, nEnd - nStart);
aResult += SwResId(STR_END_QUOTE);
}
}
return aResult;
}
OUString DenoteSpecialCharacters(const OUString & rStr)
{
OUStringBuffer aResult;
if (!rStr.isEmpty())
{
bool bStart = false;
sal_Int32 nStart = 0;
sal_Unicode cLast = 0;
for( sal_Int32 i = 0; i < rStr.getLength(); i++)
{
if (lcl_IsSpecialCharacter(rStr[i]))
{
if (cLast != rStr[i])
bStart = true;
}
else
{
if (lcl_IsSpecialCharacter(cLast))
bStart = true;
}
if (bStart)
{
aResult.append(lcl_DenotedPortion(rStr, nStart, i));
nStart = i;
bStart = false;
}
cLast = rStr[i];
}
aResult.append(lcl_DenotedPortion(rStr, nStart, rStr.getLength()));
}
else
aResult = SwRewriter::GetPlaceHolder(UndoArg2);
return aResult.makeStringAndClear();
}
SwRewriter SwUndoDelete::GetRewriter() const
{
SwRewriter aResult;
if (m_nNode != 0)
{
if (!m_sTableName.isEmpty())
{
SwRewriter aRewriter;
aRewriter.AddRule(UndoArg1, SwResId(STR_START_QUOTE));
aRewriter.AddRule(UndoArg2, m_sTableName);
aRewriter.AddRule(UndoArg3, SwResId(STR_END_QUOTE));
OUString sTmp = aRewriter.Apply(SwResId(STR_TABLE_NAME));
aResult.AddRule(UndoArg1, sTmp);
}
else
aResult.AddRule(UndoArg1, SwResId(STR_PARAGRAPHS));
}
else
{
OUString aStr;
if (m_aSttStr && m_aEndStr && m_aSttStr->isEmpty() &&
m_aEndStr->isEmpty())
{
aStr = SwResId(STR_PARAGRAPH_UNDO);
}
else
{
boost::optional<OUString> aTmpStr;
if (m_aSttStr)
aTmpStr = m_aSttStr;
else if (m_aEndStr)
aTmpStr = m_aEndStr;
if (aTmpStr)
{
aStr = DenoteSpecialCharacters(*aTmpStr);
}
else
{
aStr = SwRewriter::GetPlaceHolder(UndoArg2);
}
}
aStr = ShortenString(aStr, nUndoStringLength, SwResId(STR_LDOTS));
if (pHistory)
{
SwRewriter aRewriter = lcl_RewriterFromHistory(*pHistory);
aStr = aRewriter.Apply(aStr);
}
aResult.AddRule(UndoArg1, aStr);
}
return aResult;
}
// Every object, anchored "AtContent" will be reanchored at rPos
static void lcl_ReAnchorAtContentFlyFrames( const SwFrameFormats& rSpzArr, SwPosition &rPos, sal_uLong nOldIdx )
{
if( !rSpzArr.empty() )
{
SwFlyFrameFormat* pFormat;
const SwFormatAnchor* pAnchor;
const SwPosition* pAPos;
for( size_t n = 0; n < rSpzArr.size(); ++n )
{
pFormat = static_cast<SwFlyFrameFormat*>(rSpzArr[n]);
pAnchor = &pFormat->GetAnchor();
if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA)
{
pAPos = pAnchor->GetContentAnchor();
if( pAPos && nOldIdx == pAPos->nNode.GetIndex() )
{
SwFormatAnchor aAnch( *pAnchor );
aAnch.SetAnchor( &rPos );
pFormat->SetFormatAttr( aAnch );
}
}
}
}
}
void SwUndoDelete::UndoImpl(::sw::UndoRedoContext & rContext)
{
SwDoc& rDoc = rContext.GetDoc();
sal_uLong nCalcStt = nSttNode - m_nNdDiff;
if( m_nSectDiff && m_bBackSp )
nCalcStt += m_nSectDiff;
SwNodeIndex aIdx(rDoc.GetNodes(), nCalcStt);
SwNode* pInsNd = &aIdx.GetNode();
{ // code block so that SwPosition is detached when deleting a Node
SwPosition aPos( aIdx );
if( !m_bDelFullPara )
{
if( pInsNd->IsTableNode() )
{
pInsNd = rDoc.GetNodes().MakeTextNode( aIdx,
rDoc.GetDfltTextFormatColl() );
--aIdx;
aPos.nNode = aIdx;
aPos.nContent.Assign( pInsNd->GetContentNode(), nSttContent );
}
else
{
if( pInsNd->IsContentNode() )
aPos.nContent.Assign( static_cast<SwContentNode*>(pInsNd), nSttContent );
if( !m_bTableDelLastNd )
pInsNd = nullptr; // do not delete Node!
}
}
else
pInsNd = nullptr; // do not delete Node!
bool bNodeMove = 0 != m_nNode;
if( m_aEndStr )
{
// discard attributes since they all saved!
SwTextNode* pTextNd = aPos.nNode.GetNode().GetTextNode();
if( pTextNd && pTextNd->HasSwAttrSet() )
pTextNd->ResetAllAttr();
if( pTextNd && pTextNd->GetpSwpHints() )
pTextNd->ClearSwpHintsArr( true );
if( m_aSttStr && !m_bFromTableCopy )
{
sal_uLong nOldIdx = aPos.nNode.GetIndex();
rDoc.getIDocumentContentOperations().SplitNode( aPos, false );
// After the split all objects are anchored at the first
// paragraph, but the pHistory of the fly frame formats relies
// on anchoring at the start of the selection
// => selection backwards needs a correction.
if( m_bBackSp )
lcl_ReAnchorAtContentFlyFrames(*rDoc.GetSpzFrameFormats(), aPos, nOldIdx);
pTextNd = aPos.nNode.GetNode().GetTextNode();
}
if( pTextNd )
{
OUString const ins( pTextNd->InsertText(*m_aEndStr, aPos.nContent,
SwInsertFlags::NOHINTEXPAND) );
assert(ins.getLength() == m_aEndStr->getLength()); // must succeed
(void) ins;
// METADATA: restore
pTextNd->RestoreMetadata(m_pMetadataUndoEnd);
}
}
else if( m_aSttStr && bNodeMove )
{
SwTextNode * pNd = aPos.nNode.GetNode().GetTextNode();
if( pNd )
{
if (nSttContent < pNd->GetText().getLength())
{
sal_uLong nOldIdx = aPos.nNode.GetIndex();
rDoc.getIDocumentContentOperations().SplitNode( aPos, false );
if( m_bBackSp )
lcl_ReAnchorAtContentFlyFrames(*rDoc.GetSpzFrameFormats(), aPos, nOldIdx);
}
else
++aPos.nNode;
}
}
SwNode* pMovedNode = nullptr;
if( m_nSectDiff )
{
sal_uLong nMoveIndex = aPos.nNode.GetIndex();
int nDiff = 0;
if( m_bJoinNext )
{
nMoveIndex += m_nSectDiff + 1;
pMovedNode = &aPos.nNode.GetNode();
}
else
{
nMoveIndex -= m_nSectDiff + 1;
++nDiff;
}
SwNodeIndex aMvIdx(rDoc.GetNodes(), nMoveIndex);
SwNodeRange aRg( aPos.nNode, 0 - nDiff, aPos.nNode, 1 - nDiff );
--aPos.nNode;
if( !m_bJoinNext )
pMovedNode = &aPos.nNode.GetNode();
rDoc.GetNodes().MoveNodes(aRg, rDoc.GetNodes(), aMvIdx);
++aPos.nNode;
}
if( bNodeMove )
{
SwNodeRange aRange( *m_pMvStt, 0, *m_pMvStt, m_nNode );
SwNodeIndex aCopyIndex( aPos.nNode, -1 );
rDoc.GetUndoManager().GetUndoNodes().Copy_( aRange, aPos.nNode );
if( m_nReplaceDummy )
{
sal_uLong nMoveIndex;
if( m_bJoinNext )
{
nMoveIndex = nEndNode - m_nNdDiff;
aPos.nNode = nMoveIndex + m_nReplaceDummy;
}
else
{
aPos = SwPosition( aCopyIndex );
nMoveIndex = aPos.nNode.GetIndex() + m_nReplaceDummy + 1;
}
SwNodeIndex aMvIdx(rDoc.GetNodes(), nMoveIndex);
SwNodeRange aRg( aPos.nNode, 0, aPos.nNode, 1 );
pMovedNode = &aPos.nNode.GetNode();
rDoc.GetNodes().MoveNodes(aRg, rDoc.GetNodes(), aMvIdx);
rDoc.GetNodes().Delete( aMvIdx);
}
}
if( pMovedNode )
lcl_MakeAutoFrames(*rDoc.GetSpzFrameFormats(), pMovedNode->GetIndex());
if( m_aSttStr )
{
aPos.nNode = nSttNode - m_nNdDiff + ( m_bJoinNext ? 0 : m_nReplaceDummy );
SwTextNode * pTextNd = aPos.nNode.GetNode().GetTextNode();
// If more than a single Node got deleted, also all "Node"
// attributes were saved
if (pTextNd != nullptr)
{
if( pTextNd->HasSwAttrSet() && bNodeMove && !m_aEndStr )
pTextNd->ResetAllAttr();
if( pTextNd->GetpSwpHints() )
pTextNd->ClearSwpHintsArr( true );
// SectionNode mode and selection from top to bottom:
// -> in StartNode is still the rest of the Join => delete
aPos.nContent.Assign( pTextNd, nSttContent );
OUString const ins( pTextNd->InsertText(*m_aSttStr, aPos.nContent,
SwInsertFlags::NOHINTEXPAND) );
assert(ins.getLength() == m_aSttStr->getLength()); // must succeed
(void) ins;
// METADATA: restore
pTextNd->RestoreMetadata(m_pMetadataUndoStart);
}
}
if( pHistory )
{
pHistory->TmpRollback(&rDoc, m_nSetPos, false);
if( m_nSetPos ) // there were Footnodes/FlyFrames
{
// are there others than these ones?
if( m_nSetPos < pHistory->Count() )
{
// if so save the attributes of the others
SwHistory aHstr;
aHstr.Move( 0, pHistory.get(), m_nSetPos );
pHistory->Rollback(&rDoc);
pHistory->Move( 0, &aHstr );
}
else
{
pHistory->Rollback(&rDoc);
pHistory.reset();
}
}
}
if( m_bResetPgDesc || m_bResetPgBrk )
{
sal_uInt16 nStt = m_bResetPgDesc ? sal_uInt16(RES_PAGEDESC) : sal_uInt16(RES_BREAK);
sal_uInt16 nEnd = m_bResetPgBrk ? sal_uInt16(RES_BREAK) : sal_uInt16(RES_PAGEDESC);
SwNode* pNode = rDoc.GetNodes()[ nEndNode + 1 ];
if( pNode->IsContentNode() )
static_cast<SwContentNode*>(pNode)->ResetAttr( nStt, nEnd );
else if( pNode->IsTableNode() )
static_cast<SwTableNode*>(pNode)->GetTable().GetFrameFormat()->ResetFormatAttr( nStt, nEnd );
}
}
// delete the temporarily added Node
if( pInsNd )
rDoc.GetNodes().Delete( aIdx );
if( m_pRedlSaveData )
SetSaveData(rDoc, *m_pRedlSaveData);
AddUndoRedoPaM(rContext, true);
}
void SwUndoDelete::RedoImpl(::sw::UndoRedoContext & rContext)
{
SwPaM & rPam = AddUndoRedoPaM(rContext);
SwDoc& rDoc = *rPam.GetDoc();
if( m_pRedlSaveData )
{
const bool 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 )
{
SwUndRng aTmpRng( rPam );
RemoveIdxFromRange( rPam, false );
aTmpRng.SetPaM( rPam );
if( !m_bJoinNext ) // then restore selection from bottom to top
rPam.Exchange();
}
if( pHistory ) // are the attributes saved?
{
pHistory->SetTmpEnd( pHistory->Count() );
SwHistory aHstr;
aHstr.Move( 0, pHistory.get() );
if( m_bDelFullPara )
{
OSL_ENSURE( rPam.HasMark(), "PaM without Mark" );
DelContentIndex( *rPam.GetMark(), *rPam.GetPoint(),
DelContentType(DelContentType::AllMask | DelContentType::CheckNoCntnt) );
DelBookmarks(rPam.GetMark()->nNode, rPam.GetPoint()->nNode);
}
else
DelContentIndex( *rPam.GetMark(), *rPam.GetPoint() );
m_nSetPos = pHistory ? pHistory->Count() : 0;
pHistory->Move( m_nSetPos, &aHstr );
}
else
{
if( m_bDelFullPara )
{
OSL_ENSURE( rPam.HasMark(), "PaM without Mark" );
DelContentIndex( *rPam.GetMark(), *rPam.GetPoint(),
DelContentType(DelContentType::AllMask | DelContentType::CheckNoCntnt) );
DelBookmarks( rPam.GetMark()->nNode, rPam.GetPoint()->nNode );
}
else
DelContentIndex( *rPam.GetMark(), *rPam.GetPoint() );
m_nSetPos = pHistory ? pHistory->Count() : 0;
}
if( !m_aSttStr && !m_aEndStr )
{
SwNodeIndex aSttIdx = ( m_bDelFullPara || m_bJoinNext )
? rPam.GetMark()->nNode
: rPam.GetPoint()->nNode;
SwTableNode* pTableNd = aSttIdx.GetNode().GetTableNode();
if( pTableNd )
{
if( m_bTableDelLastNd )
{
// than add again a Node at the end
const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 );
rDoc.GetNodes().MakeTextNode( aTmpIdx,
rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) );
}
SwContentNode* pNextNd = rDoc.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();
}
else if (*rPam.GetMark() == *rPam.GetPoint())
{ // paragraph with only footnote or as-char fly, delete that
// => DelContentIndex has already deleted it! nothing to do here
assert(nEndNode == nSttNode);
return;
}
// avoid asserts from ~SwIndexReg for deleted nodes
SwPaM aTmp(*rPam.End());
if (!aTmp.Move(fnMoveForward, GoInNode))
{
*aTmp.GetPoint() = *rPam.Start();
aTmp.Move(fnMoveBackward, GoInNode);
}
assert(aTmp.GetPoint()->nNode != rPam.GetPoint()->nNode
&& aTmp.GetPoint()->nNode != rPam.GetMark()->nNode);
::PaMCorrAbs(rPam, *aTmp.GetPoint());
rPam.DeleteMark();
rDoc.GetNodes().Delete( aSttIdx, nEndNode - nSttNode );
}
else if( m_bDelFullPara )
{
// The Pam was incremented by one at Point (== end) to provide space
// for UNDO. This now needs to be reverted!
--rPam.End()->nNode;
if( rPam.GetPoint()->nNode == rPam.GetMark()->nNode )
*rPam.GetMark() = *rPam.GetPoint();
rDoc.getIDocumentContentOperations().DelFullPara( rPam );
}
else
rDoc.getIDocumentContentOperations().DeleteAndJoin( rPam );
}
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;
SwPaM & rPam = rContext.GetRepeatPaM();
SwDoc& rDoc = *rPam.GetDoc();
::sw::GroupUndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
if( !rPam.HasMark() )
{
rPam.SetMark();
rPam.Move( fnMoveForward, GoInContent );
}
if( m_bDelFullPara )
rDoc.getIDocumentContentOperations().DelFullPara( rPam );
else
rDoc.getIDocumentContentOperations().DeleteAndJoin( rPam );
rContext.m_bDeleteRepeated = true;
}
void SwUndoDelete::SetTableName(const OUString & rName)
{
m_sTableName = rName;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V1004 The 'pHistory' pointer was used unsafely after it was verified against nullptr. Check lines: 1004, 1006.