diff options
author | Michael Stahl <Michael.Stahl@cib.de> | 2019-07-11 18:37:28 +0200 |
---|---|---|
committer | Michael Stahl <Michael.Stahl@cib.de> | 2019-07-22 08:32:07 +0200 |
commit | 28b77c89dfcafae82cf2a6d85731b643ff9290e5 (patch) | |
tree | 4d26e493cebd1d83025094a3a97b9e878bc2b930 | |
parent | 22f2ecbcabf3928d5486690ca6465b7b37bc8a10 (diff) |
tdf#117185 tdf#110442 sw: bring harmony & peace to fly at-char selection
Use IsDestroyFrameAnchoredAtChar() to harmonize the at-char fly
selection across all relevant operations:
* CopyImpl: this is the most tricky one:
- the code in CopyWithFlyInFly() and CopyFlyInFlyImpl() is quite con-
voluted as it needs to do some things ignoring a partially selected
start node, while including it in other cases
- it had pre-existing bugs too that would lose a fly anchored to the
2nd (1st fully selected) node of a redline
- now it needs to copy the flys in the selection if it is inside a
single node
- another complication is that flys that already existed at the
insert position need to have their anchors corrected
- SwUndoInsLayFormat need to be created for the appropriate flys
- SwUndoInserts Undo/Redo needs to run the nested SwUndoInsLayFormat
at the appropriate time
- SwUndoInserts::UndoImpl() needs a special case to *never* delete
flys at the start/end of the selection because those are handled by
nested SwUndoInsLayFormat
- Insert File (shellio.cxx) needs adapting to the SwUndoInserts change
* DeleteRange: this just needs to delete the flys via DelFlyInRange()
* MoveRange:
- this is used by the old SwRangeRedline Show/Hide, i.e. on ODF export
- the SaveFlyInRange()/RestFlyInRange() was rather inadequate and
didn't even restore content indexes at all...
* IsShown: the sw_redlinehide code needs to check visibility against
the (inverted) extents
The selection behavior is changed so that at-char flys in the start and
end node of the selection are also selected, instead of having their
anchor moved to a different content index by the operation. This appears
more obvious and user-friendly, fixes tdf#110442, and is also more like
what Word does.
Selections exclude the start and end position except if it's a fully
selected node or at the start or end of a section (i.e. Ctrl+A should
also select every at-char fly).
A special hack is needed to keep writerfilter happy for now; it likes to
anchor flys at nodes which it then deletes in RemoveLastParagraph(),
which likely could be improved there (disposing the SwXParagraph runs
into the same problem...).
Crashes fixed by this:
tdf#117185
tdf#117215 except comment#12
tdf#124720
tdf#124721
tdf#124739
Previously fixed bugs tested:
i#97570 plus the 2 bugs that already have UITests
Change-Id: I4fec2a3c15ca0e64e5c4e99acfb04f59bb2bcf64
Reviewed-on: https://gerrit.libreoffice.org/75516
Tested-by: Jenkins
Reviewed-by: Michael Stahl <Michael.Stahl@cib.de>
22 files changed, 549 insertions, 209 deletions
diff --git a/sw/inc/undobj.hxx b/sw/inc/undobj.hxx index 38cab61b0b58..3128ffc788d5 100644 --- a/sw/inc/undobj.hxx +++ b/sw/inc/undobj.hxx @@ -35,6 +35,7 @@ struct SwPosition; class SwDoc; class SwTextFormatColl; class SwFrameFormat; +class SwFormatAnchor; class SwNodeIndex; class SwNodeRange; class SwRedlineData; @@ -134,10 +135,11 @@ enum class DelContentType : sal_uInt16 Fly = 0x02, Bkm = 0x08, AllMask = 0x0b, + ExcludeAtCharFlyAtStartEnd = 0x40, CheckNoCntnt = 0x80, }; namespace o3tl { - template<> struct typed_flags<DelContentType> : is_typed_flags<DelContentType, 0x8b> {}; + template<> struct typed_flags<DelContentType> : is_typed_flags<DelContentType, 0xcb> {}; } /// will DelContentIndex destroy a frame anchored at character at rAnchorPos? @@ -227,6 +229,13 @@ public: class SwUndoInsLayFormat; +namespace sw { + +std::unique_ptr<std::vector<SwFrameFormat*>> +GetFlysAnchoredAt(SwDoc & rDoc, sal_uLong nSttNode); + +} + // base class for insertion of Document, Glossaries and Copy class SwUndoInserts : public SwUndo, public SwUndRng, private SwUndoSaveContent { @@ -252,6 +261,10 @@ public: // Set destination range after reading. void SetInsertRange( const SwPaM&, bool bScanFlys = true, bool bSttWasTextNd = true ); + + static bool IsCreateUndoForNewFly(SwFormatAnchor const& rAnchor, + sal_uLong const nStartNode, sal_uLong const nEndNode); + std::vector<SwFrameFormat*> * GetFlysAnchoredAt() { return pFrameFormats.get(); } }; class SwUndoInsDoc : public SwUndoInserts diff --git a/sw/qa/uitest/writer_tests6/tdf107975.py b/sw/qa/uitest/writer_tests6/tdf107975.py index 202e7426bbaa..333c0f77b820 100644 --- a/sw/qa/uitest/writer_tests6/tdf107975.py +++ b/sw/qa/uitest/writer_tests6/tdf107975.py @@ -27,6 +27,34 @@ class tdf107975(UITestCase): xWriterDoc = self.xUITest.getTopFocusWindow() xWriterEdit = xWriterDoc.getChild("writer_edit") + self.assertEqual(writer_doc.getGraphicObjects().getCount(), 1) + + #Press CTRL+A and + CTRL+C + self.xUITest.executeCommand(".uno:SelectAll") + self.xUITest.executeCommand(".uno:Copy") + #Position the mouse cursor (caret) after "ABC" below the blue image + xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE": "RIGHT"})) + #Paste CTRL+V + self.xUITest.executeCommand(".uno:Paste") + self.assertEqual(writer_doc.getGraphicObjects().getCount(), 2) + #Undo paste CTRL+Z -> Crash + self.xUITest.executeCommand(".uno:Undo") + self.assertEqual(document.Text.String[0:3], "ABC") + self.assertEqual(writer_doc.getGraphicObjects().getCount(), 1) + self.xUITest.executeCommand(".uno:Redo") + self.assertEqual(writer_doc.getGraphicObjects().getCount(), 2) + self.xUITest.executeCommand(".uno:Undo") + self.assertEqual(writer_doc.getGraphicObjects().getCount(), 1) + self.xUITest.executeCommand(".uno:Redo") + self.assertEqual(writer_doc.getGraphicObjects().getCount(), 2) + self.xUITest.executeCommand(".uno:Undo") + self.assertEqual(writer_doc.getGraphicObjects().getCount(), 1) + + # try again with anchor at start of doc which is another special case + xShape = writer_doc.getGraphicObjects().getByIndex(0) + xStart = writer_doc.getText().getStart() + xShape.attach(xStart) + #Press CTRL+A and + CTRL+C self.xUITest.executeCommand(".uno:SelectAll") self.xUITest.executeCommand(".uno:Copy") @@ -34,9 +62,20 @@ class tdf107975(UITestCase): xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE": "RIGHT"})) #Paste CTRL+V self.xUITest.executeCommand(".uno:Paste") + self.assertEqual(writer_doc.getGraphicObjects().getCount(), 2) #Undo paste CTRL+Z -> Crash self.xUITest.executeCommand(".uno:Undo") + self.assertEqual(writer_doc.getGraphicObjects().getCount(), 1) self.assertEqual(document.Text.String[0:3], "ABC") + self.xUITest.executeCommand(".uno:Redo") + self.assertEqual(writer_doc.getGraphicObjects().getCount(), 2) + self.xUITest.executeCommand(".uno:Undo") + self.assertEqual(writer_doc.getGraphicObjects().getCount(), 1) + self.xUITest.executeCommand(".uno:Redo") + self.assertEqual(writer_doc.getGraphicObjects().getCount(), 2) + self.xUITest.executeCommand(".uno:Undo") + self.assertEqual(writer_doc.getGraphicObjects().getCount(), 1) self.ui_test.close_doc() + # vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/sw/source/core/doc/DocumentContentOperationsManager.cxx b/sw/source/core/doc/DocumentContentOperationsManager.cxx index a4e0e677597a..bc632acfdb1e 100644 --- a/sw/source/core/doc/DocumentContentOperationsManager.cxx +++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx @@ -2003,6 +2003,9 @@ bool DocumentContentOperationsManager::DelFullPara( SwPaM& rPam ) if (pAPos && ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + // note: here use <= not < like in + // IsDestroyFrameAnchoredAtChar() because of the increment + // of rPam in the bDoesUndo path above! aRg.aStart <= pAPos->nNode && pAPos->nNode <= aRg.aEnd ) { m_rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFly ); @@ -2044,7 +2047,7 @@ bool DocumentContentOperationsManager::MoveRange( SwPaM& rPaM, SwPosition& rPos, // Save the paragraph anchored Flys, so that they can be moved. SaveFlyArr aSaveFlyArr; - SaveFlyInRange( rPaM, rPos.nNode, aSaveFlyArr, bool( SwMoveFlags::ALLFLYS & eMvFlags ) ); + SaveFlyInRange( rPaM, rPos, aSaveFlyArr, bool( SwMoveFlags::ALLFLYS & eMvFlags ) ); // save redlines (if DOC_MOVEREDLINES is used) SaveRedlines_t aSaveRedl; @@ -2285,7 +2288,10 @@ bool DocumentContentOperationsManager::MoveRange( SwPaM& rPaM, SwPosition& rPos, *rPaM.GetPoint() = *aSavePam.End(); // Move the Flys to the new position. - RestFlyInRange( aSaveFlyArr, rPaM.Start()->nNode, &(rPos.nNode) ); + // note: rPos is at the end here; can't really tell flys that used to be + // at the start of rPam from flys that used to be at the end of rPam + // unfortunately, so some of them are going to end up with wrong anchor... + RestFlyInRange( aSaveFlyArr, *rPaM.Start(), &(rPos.nNode) ); // restore redlines (if DOC_MOVEREDLINES is used) if( !aSaveRedl.empty() ) @@ -2391,7 +2397,10 @@ bool DocumentContentOperationsManager::MoveNodeRange( SwNodeRange& rRange, SwNod // move the Flys to the new position if( !aSaveFlyArr.empty() ) - RestFlyInRange( aSaveFlyArr, aIdx, nullptr ); + { + SwPosition const tmp(aIdx); + RestFlyInRange(aSaveFlyArr, tmp, nullptr); + } // Add the Bookmarks back to the Document for(auto& rBkmk : aSaveBkmks) @@ -3296,31 +3305,36 @@ void DocumentContentOperationsManager::RemoveLeadingWhiteSpace(const SwPosition } // Copy method from SwDoc - "copy Flys in Flys" +/// note: rRg/rInsPos *exclude* a partially selected start text node; +/// pCopiedPaM *includes* a partially selected start text node void DocumentContentOperationsManager::CopyWithFlyInFly( const SwNodeRange& rRg, - const sal_Int32 nEndContentIndex, const SwNodeIndex& rInsPos, const std::pair<const SwPaM&, const SwPosition&>* pCopiedPaM /*and real insert pos*/, const bool bMakeNewFrames, const bool bDelRedlines, const bool bCopyFlyAtFly ) const { - assert(!pCopiedPaM || pCopiedPaM->first.End()->nContent == nEndContentIndex); assert(!pCopiedPaM || pCopiedPaM->first.End()->nNode == rRg.aEnd); + assert(!pCopiedPaM || pCopiedPaM->second.nNode <= rInsPos); SwDoc* pDest = rInsPos.GetNode().GetDoc(); - - SaveRedlEndPosForRestore aRedlRest( rInsPos, 0 ); - SwNodeIndex aSavePos( rInsPos, -1 ); bool bEndIsEqualEndPos = rInsPos == rRg.aEnd; - m_rDoc.GetNodes().CopyNodes( rRg, rInsPos, bMakeNewFrames, true ); + + if (rRg.aStart != rRg.aEnd) + { + SaveRedlEndPosForRestore aRedlRest( rInsPos, 0 ); + + // insert behind the already copied start node + m_rDoc.GetNodes().CopyNodes( rRg, rInsPos, bMakeNewFrames, true ); + aRedlRest.Restore(); + } + ++aSavePos; if( bEndIsEqualEndPos ) const_cast<SwNodeIndex&>(rRg.aEnd) = aSavePos; - aRedlRest.Restore(); - #if OSL_DEBUG_LEVEL > 0 { //JP 17.06.99: Bug 66973 - check count only if the selection is in @@ -3344,7 +3358,12 @@ void DocumentContentOperationsManager::CopyWithFlyInFly( { ::sw::UndoGuard const undoGuard(pDest->GetIDocumentUndoRedo()); - CopyFlyInFlyImpl( rRg, nEndContentIndex, aSavePos, bCopyFlyAtFly ); + CopyFlyInFlyImpl(rRg, pCopiedPaM ? &pCopiedPaM->first : nullptr, + // see comment below regarding use of pCopiedPaM->second + (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->nNode) + ? pCopiedPaM->second.nNode + : aSavePos, + bCopyFlyAtFly); } SwNodeRange aCpyRange( aSavePos, rInsPos ); @@ -3376,18 +3395,16 @@ void DocumentContentOperationsManager::CopyWithFlyInFly( pDest->GetNodes().DelDummyNodes( aCpyRange ); } -// TODO: there is a limitation here in that it's not possible to pass a start -// content index - which means that at-character anchored frames inside -// partial 1st paragraph of redline is not copied. -// But the DelFlyInRange() that is called from DelCopyOfSection() does not -// delete it either, and it also does not delete those on partial last para of -// redline, so copying those is suppressed here too ... +// note: for the redline Show/Hide this must be in sync with +// SwRangeRedline::CopyToSection()/DelCopyOfSection()/MoveFromSection() void DocumentContentOperationsManager::CopyFlyInFlyImpl( const SwNodeRange& rRg, - const sal_Int32 nEndContentIndex, + SwPaM const*const pCopiedPaM, const SwNodeIndex& rStartIdx, const bool bCopyFlyAtFly ) const { + assert(!pCopiedPaM || pCopiedPaM->End()->nNode == rRg.aEnd); + // First collect all Flys, sort them according to their ordering number, // and then only copy them. This maintains the ordering numbers (which are only // managed in the DrawModel). @@ -3404,9 +3421,9 @@ void DocumentContentOperationsManager::CopyFlyInFlyImpl( SwFrameFormat* pFormat = (*m_rDoc.GetSpzFrameFormats())[n]; SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); SwPosition const*const pAPos = pAnchor->GetContentAnchor(); - bool bAtContent = (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA); if ( !pAPos ) continue; + bool bAdd = false; sal_uLong nSkipAfter = pAPos->nNode.GetIndex(); sal_uLong nStart = rRg.aStart.GetIndex(); switch ( pAnchor->GetAnchorId() ) @@ -3417,59 +3434,66 @@ void DocumentContentOperationsManager::CopyFlyInFlyImpl( else if(m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) ++nStart; break; - case RndStdIds::FLY_AT_CHAR: case RndStdIds::FLY_AT_PARA: + // FIXME TODO why exclude start node, this seems very questionable and causes data loss on export if(m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) ++nStart; break; + case RndStdIds::FLY_AT_CHAR: + { + bAdd = IsDestroyFrameAnchoredAtChar(*pAPos, + pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart), + pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd)); + } + break; default: continue; } - if ( nStart > nSkipAfter ) - continue; - if ( pAPos->nNode > rRg.aEnd ) - continue; - //frames at the last source node are not always copied: - //- if the node is empty and is the last node of the document or a table cell - // or a text frame then they have to be copied - //- if the content index in this node is > 0 then paragraph and frame bound objects are copied - //- to-character bound objects are copied if their index is <= nEndContentIndex - bool bAdd = false; - if( pAPos->nNode < rRg.aEnd ) - bAdd = true; - if (!bAdd && !m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) // fdo#40599: not for redline move - { - bool bEmptyNode = false; - bool bLastNode = false; - // is the node empty? - const SwNodes& rNodes = pAPos->nNode.GetNodes(); - SwTextNode* pTextNode; - if( nullptr != ( pTextNode = pAPos->nNode.GetNode().GetTextNode() )) + if (RndStdIds::FLY_AT_CHAR != pAnchor->GetAnchorId()) + { + if (nStart > nSkipAfter) + continue; + if (pAPos->nNode > rRg.aEnd) + continue; + //frames at the last source node are not always copied: + //- if the node is empty and is the last node of the document or a table cell + // or a text frame then they have to be copied + //- if the content index in this node is > 0 then paragraph and frame bound objects are copied + //- to-character bound objects are copied if their index is <= nEndContentIndex + if (pAPos->nNode < rRg.aEnd) + bAdd = true; + if (!bAdd && !m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) // fdo#40599: not for redline move { - bEmptyNode = pTextNode->GetText().isEmpty(); - if( bEmptyNode ) + bool bEmptyNode = false; + bool bLastNode = false; + // is the node empty? + const SwNodes& rNodes = pAPos->nNode.GetNodes(); + SwTextNode *const pTextNode = pAPos->nNode.GetNode().GetTextNode(); + if (nullptr != pTextNode) { - //last node information is only necessary to know for the last TextNode - SwNodeIndex aTmp( pAPos->nNode ); - ++aTmp;//goto next node - while (aTmp.GetNode().IsEndNode()) + bEmptyNode = pTextNode->GetText().isEmpty(); + if (bEmptyNode) { - if( aTmp == rNodes.GetEndOfContent().GetIndex() ) + //last node information is only necessary to know for the last TextNode + SwNodeIndex aTmp( pAPos->nNode ); + ++aTmp;//goto next node + while (aTmp.GetNode().IsEndNode()) { - bLastNode = true; - break; + if (aTmp == rNodes.GetEndOfContent().GetIndex()) + { + bLastNode = true; + break; + } + ++aTmp; } - ++aTmp; } } - } - bAdd = bLastNode && bEmptyNode; - if( !bAdd ) - { - if( bAtContent ) - bAdd = nEndContentIndex > 0; - else - bAdd = pAPos->nContent <= nEndContentIndex; + bAdd = bLastNode && bEmptyNode; + if (!bAdd) + { + // technically old code checked nContent of AT_FLY which is pointless + bAdd = pCopiedPaM && 0 < pCopiedPaM->End()->nContent.GetIndex(); + } } } if( bAdd ) @@ -3508,7 +3532,8 @@ void DocumentContentOperationsManager::CopyFlyInFlyImpl( // Note: The anchor text node *have* to be inside the copied range. sal_uLong nAnchorTextNdNumInRange( 0 ); bool bAnchorTextNdFound( false ); - SwNodeIndex aIdx( rRg.aStart ); + // start at the first node for which flys are copied + SwNodeIndex aIdx(pCopiedPaM ? pCopiedPaM->Start()->nNode : rRg.aStart); while ( !bAnchorTextNdFound && aIdx <= rRg.aEnd ) { if ( aIdx.GetNode().IsTextNode() ) @@ -3569,7 +3594,11 @@ void DocumentContentOperationsManager::CopyFlyInFlyImpl( if ((RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) && newPos.nNode.GetNode().IsTextNode() ) { - newPos.nContent.Assign( newPos.nNode.GetNode().GetTextNode(), newPos.nContent.GetIndex() ); + // only if pCopiedPaM: care about partially selected start node + sal_Int32 const nContent = pCopiedPaM && pCopiedPaM->Start()->nNode == aAnchor.GetContentAnchor()->nNode + ? newPos.nContent.GetIndex() - pCopiedPaM->Start()->nContent.GetIndex() + : newPos.nContent.GetIndex(); + newPos.nContent.Assign(newPos.nNode.GetNode().GetTextNode(), nContent); } else { @@ -3948,7 +3977,8 @@ bool DocumentContentOperationsManager::DeleteRangeImplImpl(SwPaM & rPam) m_rDoc.getIDocumentRedlineAccess().DeleteRedline( rPam, true, RedlineType::Any ); // Delete and move all "Flys at the paragraph", which are within the Selection - DelFlyInRange(rPam.GetMark()->nNode, rPam.GetPoint()->nNode); + DelFlyInRange(rPam.GetMark()->nNode, rPam.GetPoint()->nNode, + &rPam.GetMark()->nContent, &rPam.GetPoint()->nContent); DelBookmarks( pStt->nNode, pEnd->nNode, @@ -4388,8 +4418,8 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos, SwDoc* pDoc = rPos.nNode.GetNode().GetDoc(); const bool bColumnSel = pDoc->IsClipBoard() && pDoc->IsColumnSelection(); - SwPosition* pStt = rPam.Start(); - SwPosition* pEnd = rPam.End(); + SwPosition const*const pStt = rPam.Start(); + SwPosition *const pEnd = rPam.End(); // Catch when there's no copy to do. if( !rPam.HasMark() || ( *pStt >= *pEnd && !bColumnSel ) || @@ -4409,11 +4439,19 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos, std::shared_ptr<SwUnoCursor> const pCopyPam(pDoc->CreateUnoCursor(rPos)); SwTableNumFormatMerge aTNFM( m_rDoc, *pDoc ); + std::unique_ptr<std::vector<SwFrameFormat*>> pFlys; + std::vector<SwFrameFormat*> const* pFlysAtInsPos; if (pDoc->GetIDocumentUndoRedo().DoesUndo()) { pUndo = new SwUndoCpyDoc(*pCopyPam); pDoc->GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + pFlysAtInsPos = pUndo->GetFlysAnchoredAt(); + } + else + { + pFlys = sw::GetFlysAnchoredAt(*pDoc, rPos.nNode.GetIndex()); + pFlysAtInsPos = pFlys.get(); } RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); @@ -4435,7 +4473,10 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos, bAfterTable = true; } if( !bCanMoveBack ) + { pCopyPam->GetPoint()->nNode--; + assert(pCopyPam->GetPoint()->nContent.GetIndex() == 0); + } SwNodeRange aRg( pStt->nNode, pEnd->nNode ); SwNodeIndex aInsPos( rPos.nNode ); @@ -4561,6 +4602,8 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos, pEnd->nContent -= nCpyLen; } + aRg.aStart++; + if( bOneNode ) { if (bCopyCollFormat) @@ -4569,10 +4612,12 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos, POP_NUMRULE_STATE } + // copy at-char flys in rPam + aInsPos = *pDestTextNd; // update to new (start) node for flys + CopyFlyInFlyImpl(aRg, &rPam, aInsPos, false); + break; } - - aRg.aStart++; } } else if( pDestTextNd ) @@ -4670,9 +4715,9 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos, } } + SfxItemSet aBrkSet( pDoc->GetAttrPool(), aBreakSetRange ); if( bCopyAll || aRg.aStart != aRg.aEnd ) { - SfxItemSet aBrkSet( pDoc->GetAttrPool(), aBreakSetRange ); if (pSttTextNd && bCopyCollFormat && pDestTextNd->HasSwAttrSet()) { aBrkSet.Put( *pDestTextNd->GetpSwAttrSet() ); @@ -4681,7 +4726,9 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos, if( SfxItemState::SET == aBrkSet.GetItemState( RES_PAGEDESC, false ) ) pDestTextNd->ResetAttr( RES_PAGEDESC ); } + } + { SwPosition startPos(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1), SwIndex(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1).GetNode().GetContentNode())); if (bCanMoveBack) @@ -4695,16 +4742,48 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos, if( aInsPos == pEnd->nNode ) { SwNodeIndex aSaveIdx( aInsPos, -1 ); - CopyWithFlyInFly( aRg, 0, aInsPos, &tmp, bMakeNewFrames, false ); + assert(pStt->nNode != pEnd->nNode); + pEnd->nContent = 0; // TODO why this? + CopyWithFlyInFly( aRg, aInsPos, &tmp, bMakeNewFrames, false ); ++aSaveIdx; pEnd->nNode = aSaveIdx; pEnd->nContent.Assign( aSaveIdx.GetNode().GetTextNode(), 0 ); } else - CopyWithFlyInFly( aRg, pEnd->nContent.GetIndex(), aInsPos, &tmp, bMakeNewFrames, false ); + CopyWithFlyInFly( aRg, aInsPos, &tmp, bMakeNewFrames, false ); bCopyBookmarks = false; + } + + // at-char anchors post SplitNode are on index 0 of 2nd node and will + // remain there - move them back to the start (end would also work?) + if (pFlysAtInsPos) + { + // init *again* - because CopyWithFlyInFly moved startPos + SwPosition startPos(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1), + SwIndex(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1).GetNode().GetContentNode())); + if (bCanMoveBack) + { // pCopyPam is actually 1 before the copy range so move it fwd + SwPaM temp(*pCopyPam->GetPoint()); + temp.Move(fnMoveForward, GoInContent); + startPos = *temp.GetPoint(); + } + assert(startPos.nNode.GetNode().IsContentNode()); + for (SwFrameFormat * pFly : *pFlysAtInsPos) + { + SwFormatAnchor const*const pAnchor = &pFly->GetAnchor(); + if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + SwFormatAnchor anchor(*pAnchor); + anchor.SetAnchor( &startPos ); + pFly->SetFormatAttr(anchor); + } + } + } + + if (bCopyAll || aRg.aStart != aRg.aEnd) + { // Put the breaks back into the first node if( aBrkSet.Count() && nullptr != ( pDestTextNd = pDoc->GetNodes()[ pCopyPam->GetPoint()->nNode.GetIndex()+1 ]->GetTextNode())) diff --git a/sw/source/core/doc/DocumentLayoutManager.cxx b/sw/source/core/doc/DocumentLayoutManager.cxx index 68e08bad1b80..11ed4010f7ff 100644 --- a/sw/source/core/doc/DocumentLayoutManager.cxx +++ b/sw/source/core/doc/DocumentLayoutManager.cxx @@ -426,7 +426,7 @@ SwFrameFormat *DocumentLayoutManager::CopyLayoutFormat( //contact object itself. They should be managed by SwUndoInsLayFormat. const ::sw::DrawUndoGuard drawUndoGuard(m_rDoc.GetIDocumentUndoRedo()); - pSrcDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly( aRg, 0, aIdx, nullptr, false, true, true ); + pSrcDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aIdx, nullptr, false, true, true); } else { diff --git a/sw/source/core/doc/doccomp.cxx b/sw/source/core/doc/doccomp.cxx index 3054d17940f3..0df9cdf55068 100644 --- a/sw/source/core/doc/doccomp.cxx +++ b/sw/source/core/doc/doccomp.cxx @@ -1532,7 +1532,7 @@ void CompareData::ShowDelete( SwNodeIndex aInsPos( *pLineNd, nOffset ); SwNodeIndex aSavePos( aInsPos, -1 ); - rData.rDoc.GetDocumentContentOperationsManager().CopyWithFlyInFly( aRg, 0, aInsPos ); + rData.rDoc.GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aInsPos); rDoc.getIDocumentState().SetModified(); ++aSavePos; diff --git a/sw/source/core/doc/docdesc.cxx b/sw/source/core/doc/docdesc.cxx index ca14991405c5..c7a59f30e955 100644 --- a/sw/source/core/doc/docdesc.cxx +++ b/sw/source/core/doc/docdesc.cxx @@ -299,7 +299,7 @@ void SwDoc::CopyMasterHeader(const SwPageDesc &rChged, const SwFormatHeader &rHe aTmp = *pSttNd->EndOfSectionNode(); GetNodes().Copy_( aRange, aTmp, false ); aTmp = *pSttNd; - GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, 0, aTmp); + GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, nullptr, aTmp); pFormat->SetFormatAttr( SwFormatContent( pSttNd ) ); rDescFrameFormat.SetFormatAttr( SwFormatHeader( pFormat ) ); @@ -371,7 +371,7 @@ void SwDoc::CopyMasterFooter(const SwPageDesc &rChged, const SwFormatFooter &rFo aTmp = *pSttNd->EndOfSectionNode(); GetNodes().Copy_( aRange, aTmp, false ); aTmp = *pSttNd; - GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, 0, aTmp); + GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, nullptr, aTmp); pFormat->SetFormatAttr( SwFormatContent( pSttNd ) ); rDescFrameFormat.SetFormatAttr( SwFormatFooter( pFormat ) ); diff --git a/sw/source/core/doc/docedt.cxx b/sw/source/core/doc/docedt.cxx index baf32805d516..1388bdc1b53a 100644 --- a/sw/source/core/doc/docedt.cxx +++ b/sw/source/core/doc/docedt.cxx @@ -45,6 +45,7 @@ #include <docedt.hxx> #include <frmfmt.hxx> #include <ndtxt.hxx> +#include <undobj.hxx> #include <vector> #include <com/sun/star/linguistic2/XProofreadingIterator.hpp> @@ -54,27 +55,47 @@ using namespace ::com::sun::star::linguistic2; using namespace ::com::sun::star::i18n; -void RestFlyInRange( SaveFlyArr & rArr, const SwNodeIndex& rSttIdx, +void RestFlyInRange( SaveFlyArr & rArr, const SwPosition& rStartPos, const SwNodeIndex* pInsertPos ) { - SwPosition aPos( rSttIdx ); + SwPosition aPos(rStartPos); for(SaveFly & rSave : rArr) { // create new anchor SwFrameFormat* pFormat = rSave.pFrameFormat; + SwFormatAnchor aAnchor( pFormat->GetAnchor() ); - if( rSave.bInsertPosition ) + if (rSave.isAtInsertNode) { if( pInsertPos != nullptr ) - aPos.nNode = *pInsertPos; + { + if (aAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA) + { + aPos.nNode = *pInsertPos; + aPos.nContent.Assign(dynamic_cast<SwIndexReg*>(&aPos.nNode.GetNode()), + rSave.nContentIndex); + } + else + { + assert(aAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR); + aPos = rStartPos; + } + } else - aPos.nNode = rSttIdx.GetIndex(); + { + aPos.nNode = rStartPos.nNode; + aPos.nContent.Assign(dynamic_cast<SwIndexReg*>(&aPos.nNode.GetNode()), 0); + } } else - aPos.nNode = rSttIdx.GetIndex() + rSave.nNdDiff; + { + aPos.nNode = rStartPos.nNode.GetIndex() + rSave.nNdDiff; + aPos.nContent.Assign(dynamic_cast<SwIndexReg*>(&aPos.nNode.GetNode()), + rSave.nNdDiff == 0 + ? rStartPos.nContent.GetIndex() + rSave.nContentIndex + : rSave.nContentIndex); + } - aPos.nContent.Assign(dynamic_cast<SwIndexReg*>(&aPos.nNode.GetNode()), 0); - SwFormatAnchor aAnchor( pFormat->GetAnchor() ); aAnchor.SetAnchor( &aPos ); pFormat->GetDoc()->GetSpzFrameFormats()->push_back( pFormat ); // SetFormatAttr should call Modify() and add it to the node @@ -83,7 +104,7 @@ void RestFlyInRange( SaveFlyArr & rArr, const SwNodeIndex& rSttIdx, if (pCNd && pCNd->getLayoutFrame(pFormat->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr)) pFormat->MakeFrames(); } - sw::CheckAnchoredFlyConsistency(*rSttIdx.GetNode().GetDoc()); + sw::CheckAnchoredFlyConsistency(*rStartPos.nNode.GetNode().GetDoc()); } void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr ) @@ -100,6 +121,9 @@ void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr ) rRg.aStart <= pAPos->nNode && pAPos->nNode < rRg.aEnd ) { SaveFly aSave( pAPos->nNode.GetIndex() - rRg.aStart.GetIndex(), + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) + ? pAPos->nContent.GetIndex() + : 0, pFormat, false ); rArr.push_back( aSave ); pFormat->DelFrames(); @@ -113,7 +137,7 @@ void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr ) sw::CheckAnchoredFlyConsistency(*rRg.aStart.GetNode().GetDoc()); } -void SaveFlyInRange( const SwPaM& rPam, const SwNodeIndex& rInsPos, +void SaveFlyInRange( const SwPaM& rPam, const SwPosition& rInsPos, SaveFlyArr& rArr, bool bMoveAllFlys ) { SwFrameFormats& rFormats = *rPam.GetPoint()->nNode.GetNode().GetDoc()->GetSpzFrameFormats(); @@ -142,12 +166,14 @@ void SaveFlyInRange( const SwPaM& rPam, const SwNodeIndex& rInsPos, (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && // do not move if the InsPos is in the ContentArea of the Fly ( nullptr == ( pContentIdx = pFormat->GetContent().GetContentIdx() ) || - !( *pContentIdx < rInsPos && - rInsPos < pContentIdx->GetNode().EndOfSectionIndex() )) ) + !(*pContentIdx < rInsPos.nNode && + rInsPos.nNode < pContentIdx->GetNode().EndOfSectionIndex()))) { bool bInsPos = false; - if( !bMoveAllFlys && rEndNdIdx == pAPos->nNode ) + if (!bMoveAllFlys + && RndStdIds::FLY_AT_CHAR != pAnchor->GetAnchorId() + && rEndNdIdx == pAPos->nNode) { // Do not touch Anchor, if only a part of the EndNode // or the whole EndNode is identical with the SttNode @@ -160,12 +186,23 @@ void SaveFlyInRange( const SwPaM& rPam, const SwNodeIndex& rInsPos, pFormat->SetFormatAttr( aAnchor ); } } - else if( ( rSttNdIdx.GetIndex() + nSttOff <= pAPos->nNode.GetIndex() - && pAPos->nNode.GetIndex() <= rEndNdIdx.GetIndex() - nOff ) || - ( bInsPos = (rInsPos == pAPos->nNode) )) - + else if ( (//bMoveAllFlys ... no do not check - all callers are actually from redline code, from the MoveToSection case; so check bMoveAllFlys only for AT_PARA! + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) + && IsDestroyFrameAnchoredAtChar(*pAPos, *rPam.Start(), *rPam.End())) + || (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId() + && rSttNdIdx.GetIndex() + nSttOff <= pAPos->nNode.GetIndex() + && pAPos->nNode.GetIndex() <= rEndNdIdx.GetIndex() - nOff) + || (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId() + && (bInsPos = (rInsPos.nNode == pAPos->nNode))) + || (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId() + && (bInsPos = (rInsPos == *pAPos)))) { SaveFly aSave( pAPos->nNode.GetIndex() - rSttNdIdx.GetIndex(), + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) + ? (pAPos->nNode == rSttNdIdx) + ? pAPos->nContent.GetIndex() - rPam.Start()->nContent.GetIndex() + : pAPos->nContent.GetIndex() + : 0, pFormat, bInsPos ); rArr.push_back( aSave ); pFormat->DelFrames(); @@ -183,8 +220,18 @@ void SaveFlyInRange( const SwPaM& rPam, const SwNodeIndex& rInsPos, /// Delete and move all Flys at the paragraph, that are within the selection. /// If there is a Fly at the SPoint, it is moved onto the Mark. void DelFlyInRange( const SwNodeIndex& rMkNdIdx, - const SwNodeIndex& rPtNdIdx ) + const SwNodeIndex& rPtNdIdx, + SwIndex const*const pMkIdx, SwIndex const*const pPtIdx) { + assert((pMkIdx == nullptr) == (pPtIdx == nullptr)); + SwPosition const point(pPtIdx + ? SwPosition(rPtNdIdx, *pPtIdx) + : SwPosition(rPtNdIdx)); + SwPosition const mark(pPtIdx + ? SwPosition(rMkNdIdx, *pMkIdx) + : SwPosition(rMkNdIdx)); + SwPosition const& rStart = mark <= point ? mark : point; + SwPosition const& rEnd = mark <= point ? point : mark; const bool bDelFwrd = rMkNdIdx.GetIndex() <= rPtNdIdx.GetIndex(); SwDoc* pDoc = rMkNdIdx.GetNode().GetDoc(); @@ -195,14 +242,18 @@ void DelFlyInRange( const SwNodeIndex& rMkNdIdx, const SwFormatAnchor &rAnch = pFormat->GetAnchor(); SwPosition const*const pAPos = rAnch.GetContentAnchor(); if (pAPos && - ((rAnch.GetAnchorId() == RndStdIds::FLY_AT_PARA) || - (rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR)) && - ( bDelFwrd - ? rMkNdIdx < pAPos->nNode && pAPos->nNode <= rPtNdIdx - : rPtNdIdx <= pAPos->nNode && pAPos->nNode < rMkNdIdx )) + (((rAnch.GetAnchorId() == RndStdIds::FLY_AT_PARA) + && (bDelFwrd + ? rMkNdIdx < pAPos->nNode && pAPos->nNode <= rPtNdIdx + : rPtNdIdx <= pAPos->nNode && pAPos->nNode < rMkNdIdx)) + || ((rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + && IsDestroyFrameAnchoredAtChar(*pAPos, rStart, rEnd, pPtIdx + ? DelContentType::AllMask + : DelContentType::AllMask|DelContentType::CheckNoCntnt)))) { // Only move the Anchor?? - if( rPtNdIdx == pAPos->nNode ) + if ((rAnch.GetAnchorId() == RndStdIds::FLY_AT_PARA) + && rPtNdIdx == pAPos->nNode ) { SwFormatAnchor aAnch( pFormat->GetAnchor() ); SwPosition aPos( rMkNdIdx ); diff --git a/sw/source/core/doc/docfmt.cxx b/sw/source/core/doc/docfmt.cxx index 5bd36426a600..10bbb0d097c8 100644 --- a/sw/source/core/doc/docfmt.cxx +++ b/sw/source/core/doc/docfmt.cxx @@ -1410,7 +1410,7 @@ void SwDoc::CopyPageDescHeaderFooterImpl( bool bCpyHeader, aTmpIdx = *pSttNd->EndOfSectionNode(); rSrcNds.Copy_( aRg, aTmpIdx ); aTmpIdx = *pSttNd; - rSrcFormat.GetDoc()->GetDocumentContentOperationsManager().CopyFlyInFlyImpl( aRg, 0, aTmpIdx ); + rSrcFormat.GetDoc()->GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRg, nullptr, aTmpIdx); pNewFormat->SetFormatAttr( SwFormatContent( pSttNd )); } else diff --git a/sw/source/core/doc/docglbl.cxx b/sw/source/core/doc/docglbl.cxx index d4522f54714e..771512cd2ede 100644 --- a/sw/source/core/doc/docglbl.cxx +++ b/sw/source/core/doc/docglbl.cxx @@ -315,7 +315,7 @@ bool SwDoc::SplitDoc( sal_uInt16 eDocType, const OUString& rPath, bool bOutline, pDoc->GetNodes().Delete( aIdx ); // All Flys in the section - GetDocumentContentOperationsManager().CopyFlyInFlyImpl( aRg, 0, aIdx ); + GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRg, nullptr, aIdx); // And what's with all the Bookmarks? // ????? diff --git a/sw/source/core/doc/docredln.cxx b/sw/source/core/doc/docredln.cxx index 6b420cf1351e..d18a30541dcf 100644 --- a/sw/source/core/doc/docredln.cxx +++ b/sw/source/core/doc/docredln.cxx @@ -1419,7 +1419,7 @@ void SwRangeRedline::CopyToSection() { SwNodeIndex aInsPos( *pSttNd->EndOfSectionNode() ); SwNodeRange aRg( pStt->nNode, 0, pEnd->nNode, 1 ); - pDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly( aRg, 0, aInsPos ); + pDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aInsPos); } } m_pContentSect = new SwNodeIndex( *pSttNd ); diff --git a/sw/source/core/doc/tblcpy.cxx b/sw/source/core/doc/tblcpy.cxx index 43e06fce73e2..4975217f16e1 100644 --- a/sw/source/core/doc/tblcpy.cxx +++ b/sw/source/core/doc/tblcpy.cxx @@ -518,7 +518,7 @@ static void lcl_CpyBox( const SwTable& rCpyTable, const SwTableBox* pCpyBox, SwNodeIndex aSavePos( aInsIdx, -1 ); if (pRg) - pCpyDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly( *pRg, 0, aInsIdx, nullptr, false ); + pCpyDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(*pRg, aInsIdx, nullptr, false); else pDoc->GetNodes().MakeTextNode( aInsIdx, pDoc->GetDfltTextFormatColl() ); ++aSavePos; diff --git a/sw/source/core/doc/tblrwcl.cxx b/sw/source/core/doc/tblrwcl.cxx index 1084e4c77475..b7b28213e2ca 100644 --- a/sw/source/core/doc/tblrwcl.cxx +++ b/sw/source/core/doc/tblrwcl.cxx @@ -1896,7 +1896,7 @@ static void lcl_CopyBoxToDoc(FndBox_ const& rFndBox, CpyPara *const pCpyPara) *rFndBox.GetBox()->GetSttNd()->EndOfSectionNode() ); SwNodeIndex aInsIdx( *pBox->GetSttNd(), 1 ); - pFromDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly( aCpyRg, 0, aInsIdx, nullptr, false ); + pFromDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(aCpyRg, aInsIdx, nullptr, false); // Delete the initial TextNode pCpyPara->pDoc->GetNodes().Delete( aInsIdx ); } diff --git a/sw/source/core/docnode/section.cxx b/sw/source/core/docnode/section.cxx index 8ea531804045..be3d20e48869 100644 --- a/sw/source/core/docnode/section.cxx +++ b/sw/source/core/docnode/section.cxx @@ -1352,7 +1352,7 @@ static void lcl_UpdateLinksInSect( SwBaseLink& rUpdLnk, SwSectionNode& rSectNd ) SwTableNumFormatMerge aTNFM( *pSrcDoc, *pDoc ); - pSrcDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly( *pCpyRg, 0, rInsPos, nullptr, bCreateFrame ); + pSrcDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(*pCpyRg, rInsPos, nullptr, bCreateFrame); ++aSave; if( !bCreateFrame ) diff --git a/sw/source/core/inc/DocumentContentOperationsManager.hxx b/sw/source/core/inc/DocumentContentOperationsManager.hxx index f02bf2fb46e7..d6f8f8eb2a09 100644 --- a/sw/source/core/inc/DocumentContentOperationsManager.hxx +++ b/sw/source/core/inc/DocumentContentOperationsManager.hxx @@ -102,14 +102,13 @@ public: //Non-Interface methods void CopyWithFlyInFly( const SwNodeRange& rRg, - const sal_Int32 nEndContentIndex, const SwNodeIndex& rInsPos, const std::pair<const SwPaM&, const SwPosition&> * pCopiedPaM = nullptr, bool bMakeNewFrames = true, bool bDelRedlines = true, bool bCopyFlyAtFly = false ) const; void CopyFlyInFlyImpl( const SwNodeRange& rRg, - const sal_Int32 nEndContentIndex, + SwPaM const*const pCopiedPaM, const SwNodeIndex& rStartIdx, const bool bCopyFlyAtFly = false ) const; diff --git a/sw/source/core/inc/frmtool.hxx b/sw/source/core/inc/frmtool.hxx index 4340e40babc3..a127579c59ad 100644 --- a/sw/source/core/inc/frmtool.hxx +++ b/sw/source/core/inc/frmtool.hxx @@ -60,11 +60,13 @@ void AppendObjs( const SwFrameFormats *pTable, sal_uLong nIndex, void AppendObjsOfNode(SwFrameFormats const* pTable, sal_uLong nIndex, SwFrame * pFrame, SwPageFrame * pPage, SwDoc * pDoc, std::vector<sw::Extent>::const_iterator const* pIter, - std::vector<sw::Extent>::const_iterator const* pEnd); + std::vector<sw::Extent>::const_iterator const* pEnd, + SwTextNode const* pFirstNode, SwTextNode const* pLastNode); void RemoveHiddenObjsOfNode(SwTextNode const& rNode, std::vector<sw::Extent>::const_iterator const* pIter, - std::vector<sw::Extent>::const_iterator const* pEnd); + std::vector<sw::Extent>::const_iterator const* pEnd, + SwTextNode const* pFirstNode, SwTextNode const* pLastNode); bool IsAnchoredObjShown(SwTextFrame const& rFrame, SwFormatAnchor const& rAnchor); diff --git a/sw/source/core/inc/mvsave.hxx b/sw/source/core/inc/mvsave.hxx index f070a11a5bd6..c8ff124af161 100644 --- a/sw/source/core/inc/mvsave.hxx +++ b/sw/source/core/inc/mvsave.hxx @@ -99,24 +99,30 @@ void DelBookmarks(const SwNodeIndex& rStt, struct SaveFly { sal_uLong const nNdDiff; /// relative node difference + sal_Int32 const nContentIndex; ///< index in node SwFrameFormat* const pFrameFormat; /// the fly's frame format - bool const bInsertPosition; /// if true, anchor _at_ insert position + bool const isAtInsertNode; ///< if true, anchor _at_ insert node index - SaveFly( sal_uLong nNodeDiff, SwFrameFormat* pFormat, bool bInsert ) - : nNdDiff( nNodeDiff ), pFrameFormat( pFormat ), bInsertPosition( bInsert ) + SaveFly( sal_uLong nNodeDiff, sal_Int32 const nCntntIdx, SwFrameFormat* pFormat, bool bInsert ) + : nNdDiff(nNodeDiff) + , nContentIndex(nCntntIdx) + , pFrameFormat(pFormat) + , isAtInsertNode(bInsert) { } }; typedef std::deque< SaveFly > SaveFlyArr; -void RestFlyInRange( SaveFlyArr& rArr, const SwNodeIndex& rSttIdx, +void RestFlyInRange( SaveFlyArr& rArr, const SwPosition& rSttIdx, const SwNodeIndex* pInsPos ); void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr ); -void SaveFlyInRange( const SwPaM& rPam, const SwNodeIndex& rInsPos, +void SaveFlyInRange( const SwPaM& rPam, const SwPosition& rInsPos, SaveFlyArr& rArr, bool bMoveAllFlys ); void DelFlyInRange( const SwNodeIndex& rMkNdIdx, - const SwNodeIndex& rPtNdIdx ); + const SwNodeIndex& rPtNdIdx, + SwIndex const* pMkIdx = nullptr, + SwIndex const* pPtIdx = nullptr); class SwDataChanged { diff --git a/sw/source/core/layout/frmtool.cxx b/sw/source/core/layout/frmtool.cxx index 5564576079bc..416037b25fbd 100644 --- a/sw/source/core/layout/frmtool.cxx +++ b/sw/source/core/layout/frmtool.cxx @@ -65,6 +65,7 @@ #include <objectformatter.hxx> #include <calbck.hxx> #include <ndtxt.hxx> +#include <undobj.hxx> #include <DocumentSettingManager.hxx> #include <IDocumentDrawModelAccess.hxx> #include <IDocumentTimerAccess.hxx> @@ -1041,7 +1042,8 @@ void AppendObj(SwFrame *const pFrame, SwPageFrame *const pPage, SwFrameFormat *c static bool IsShown(sal_uLong const nIndex, const SwFormatAnchor & rAnch, std::vector<sw::Extent>::const_iterator const*const pIter, - std::vector<sw::Extent>::const_iterator const*const pEnd) + std::vector<sw::Extent>::const_iterator const*const pEnd, + SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode) { assert(!pIter || *pIter == *pEnd || (*pIter)->pNode->GetIndex() == nIndex); SwPosition const& rAnchor(*rAnch.GetContentAnchor()); @@ -1049,30 +1051,83 @@ static bool IsShown(sal_uLong const nIndex, { return false; } - if (pIter && rAnch.GetAnchorId() != RndStdIds::FLY_AT_PARA - // sw_redlinehide: we want to hide AT_CHAR, but currently can't - // because Delete and Accept Redline don't delete them! - && rAnch.GetAnchorId() != RndStdIds::FLY_AT_CHAR) + if (pIter && rAnch.GetAnchorId() != RndStdIds::FLY_AT_PARA) { // note: frames are not sorted by anchor position. assert(pEnd); + assert(pFirstNode); + assert(pLastNode); assert(rAnch.GetAnchorId() != RndStdIds::FLY_AT_FLY); for (auto iter = *pIter; iter != *pEnd; ++iter) { + assert(iter->nStart != iter->nEnd); // TODO possible? assert(iter->pNode->GetIndex() == nIndex); if (rAnchor.nContent.GetIndex() < iter->nStart) { return false; } - // for AS_CHAR obviously must be < - // for AT_CHAR it is questionable whether < or <= should be used - // and there is the additional corner case of Len() to consider - // prefer < for now for symmetry (and inverted usage with - // "hidden") and handle special case explicitly - if (rAnchor.nContent.GetIndex() < iter->nEnd - || iter->nEnd == iter->pNode->Len()) + if (rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + // if there is an extent then obviously the node was not + // deleted fully... + // show if start <= pos <= end + // *or* if first-node/0 *and* not StartOfSection + // *or* if last-node/Len *and* not EndOfSection + + // first determine the extent to compare to, then + // construct start/end positions for the deletion *before* the + // extent and compare once. + // the interesting corner cases are on the edge of the extent! + // no need to check for > the last extent because those + // are never visible. + if (rAnchor.nContent.GetIndex() <= iter->nEnd) + { + if (iter->nStart == 0) + { + return true; + } + else + { + SwPosition const start( + const_cast<SwTextNode&>( + iter == *pIter + ? *pFirstNode // simplification + : *iter->pNode), + iter == *pIter // first extent? + ? iter->pNode == pFirstNode + ? 0 // at start of 1st node + : pFirstNode->Len() // previous node; simplification but should get right result + : (iter-1)->nEnd); // previous extent + SwPosition const end(*iter->pNode, iter->nStart); + return !IsDestroyFrameAnchoredAtChar(rAnchor, start, end); + } + } + else if (iter == *pEnd - 1) // special case: after last extent + { + if (iter->nEnd == iter->pNode->Len()) + { + return true; // special case: end of node + } + else + { + SwPosition const start(*iter->pNode, iter->nEnd); + SwPosition const end( + const_cast<SwTextNode&>(*pLastNode), // simplification + iter->pNode == pLastNode + ? iter->pNode->Len() + : 0); + return !IsDestroyFrameAnchoredAtChar(rAnchor, start, end); + } + } + } + else { - return true; + assert(rAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR); + // for AS_CHAR obviously must be < + if (rAnchor.nContent.GetIndex() < iter->nEnd) + { + return true; + } } } return false; @@ -1085,7 +1140,8 @@ static bool IsShown(sal_uLong const nIndex, void RemoveHiddenObjsOfNode(SwTextNode const& rNode, std::vector<sw::Extent>::const_iterator const*const pIter, - std::vector<sw::Extent>::const_iterator const*const pEnd) + std::vector<sw::Extent>::const_iterator const*const pEnd, + SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode) { std::vector<SwFrameFormat*> const*const pFlys(rNode.GetAnchoredFlys()); if (!pFlys) @@ -1100,7 +1156,7 @@ void RemoveHiddenObjsOfNode(SwTextNode const& rNode, && RES_DRAWFRMFMT == pFrameFormat->Which())) { assert(rAnchor.GetContentAnchor()->nNode.GetIndex() == rNode.GetIndex()); - if (!IsShown(rNode.GetIndex(), rAnchor, pIter, pEnd)) + if (!IsShown(rNode.GetIndex(), rAnchor, pIter, pEnd, pFirstNode, pLastNode)) { pFrameFormat->DelFrames(); } @@ -1111,7 +1167,8 @@ void RemoveHiddenObjsOfNode(SwTextNode const& rNode, void AppendObjsOfNode(SwFrameFormats const*const pTable, sal_uLong const nIndex, SwFrame *const pFrame, SwPageFrame *const pPage, SwDoc *const pDoc, std::vector<sw::Extent>::const_iterator const*const pIter, - std::vector<sw::Extent>::const_iterator const*const pEnd) + std::vector<sw::Extent>::const_iterator const*const pEnd, + SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode) { #if OSL_DEBUG_LEVEL > 0 std::vector<SwFrameFormat*> checkFormats; @@ -1120,7 +1177,7 @@ void AppendObjsOfNode(SwFrameFormats const*const pTable, sal_uLong const nIndex, SwFrameFormat *pFormat = (*pTable)[i]; const SwFormatAnchor &rAnch = pFormat->GetAnchor(); if ( rAnch.GetContentAnchor() && - IsShown(nIndex, rAnch, pIter, pEnd)) + IsShown(nIndex, rAnch, pIter, pEnd, pFirstNode, pLastNode)) { checkFormats.push_back( pFormat ); } @@ -1136,7 +1193,7 @@ void AppendObjsOfNode(SwFrameFormats const*const pTable, sal_uLong const nIndex, SwFrameFormat *const pFormat = (*pFlys)[it]; const SwFormatAnchor &rAnch = pFormat->GetAnchor(); if ( rAnch.GetContentAnchor() && - IsShown(nIndex, rAnch, pIter, pEnd)) + IsShown(nIndex, rAnch, pIter, pEnd, pFirstNode, pLastNode)) { #if OSL_DEBUG_LEVEL > 0 std::vector<SwFrameFormat*>::iterator checkPos = std::find( checkFormats.begin(), checkFormats.end(), pFormat ); @@ -1170,7 +1227,8 @@ void AppendObjs(const SwFrameFormats *const pTable, sal_uLong const nIndex, if (iter == pMerged->extents.end() || iter->pNode != pNode) { - AppendObjsOfNode(pTable, pNode->GetIndex(), pFrame, pPage, pDoc, &iterFirst, &iter); + AppendObjsOfNode(pTable, pNode->GetIndex(), pFrame, pPage, pDoc, + &iterFirst, &iter, pMerged->pFirstNode, pMerged->pLastNode); sal_uLong const until = iter == pMerged->extents.end() ? pMerged->pLastNode->GetIndex() + 1 : iter->pNode->GetIndex(); @@ -1181,7 +1239,7 @@ void AppendObjs(const SwFrameFormats *const pTable, sal_uLong const nIndex, SwNode const*const pTmp(pNode->GetNodes()[i]); if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst) { - AppendObjsOfNode(pTable, pTmp->GetIndex(), pFrame, pPage, pDoc, &iter, &iter); + AppendObjsOfNode(pTable, pTmp->GetIndex(), pFrame, pPage, pDoc, &iter, &iter, pMerged->pFirstNode, pMerged->pLastNode); } } if (iter == pMerged->extents.end()) @@ -1195,12 +1253,12 @@ void AppendObjs(const SwFrameFormats *const pTable, sal_uLong const nIndex, } else { - return AppendObjsOfNode(pTable, nIndex, pFrame, pPage, pDoc, nullptr, nullptr); + return AppendObjsOfNode(pTable, nIndex, pFrame, pPage, pDoc, nullptr, nullptr, nullptr, nullptr); } } else { - return AppendObjsOfNode(pTable, nIndex, pFrame, pPage, pDoc, nullptr, nullptr); + return AppendObjsOfNode(pTable, nIndex, pFrame, pPage, pDoc, nullptr, nullptr, nullptr, nullptr); } } @@ -1225,7 +1283,8 @@ bool IsAnchoredObjShown(SwTextFrame const& rFrame, SwFormatAnchor const& rAnchor assert(pNode->GetRedlineMergeFlag() != SwNode::Merge::Hidden); if (pNode == &pAnchor->nNode.GetNode()) { - ret = IsShown(pNode->GetIndex(), rAnchor, &iterFirst, &iter); + ret = IsShown(pNode->GetIndex(), rAnchor, &iterFirst, &iter, + pMergedPara->pFirstNode, pMergedPara->pLastNode); break; } if (iter == pMergedPara->extents.end()) diff --git a/sw/source/core/layout/wsfrm.cxx b/sw/source/core/layout/wsfrm.cxx index cfd0e4ff5024..95af7d95dda2 100644 --- a/sw/source/core/layout/wsfrm.cxx +++ b/sw/source/core/layout/wsfrm.cxx @@ -4180,18 +4180,19 @@ static void AddRemoveFlysForNode( SwPageFrame *const pPage, SwTextNode const*const pNode, std::vector<sw::Extent>::const_iterator & rIterFirst, - std::vector<sw::Extent>::const_iterator const& rIterEnd) + std::vector<sw::Extent>::const_iterator const& rIterEnd, + SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode) { if (pNode == &rTextNode) { // remove existing hidden at-char anchored flys - RemoveHiddenObjsOfNode(rTextNode, &rIterFirst, &rIterEnd); + RemoveHiddenObjsOfNode(rTextNode, &rIterFirst, &rIterEnd, pFirstNode, pLastNode); } else if (rTextNode.GetIndex() < pNode->GetIndex()) { // pNode's frame has been deleted by CheckParaRedlineMerge() AppendObjsOfNode(&rTable, pNode->GetIndex(), &rFrame, pPage, rTextNode.GetDoc(), - &rIterFirst, &rIterEnd); + &rIterFirst, &rIterEnd, pFirstNode, pLastNode); if (pSkipped) { // if a fly has been added by AppendObjsOfNode, it must be skipped; if not, then it doesn't matter if it's skipped or not because it has no frames and because of that it would be skipped anyway @@ -4239,7 +4240,8 @@ void AddRemoveFlysAnchoredToFrameStartingAtNode( || iter->pNode != pNode) { AddRemoveFlysForNode(rFrame, rTextNode, pSkipped, rTable, pPage, - pNode, iterFirst, iter); + pNode, iterFirst, iter, + pMerged->pFirstNode, pMerged->pLastNode); sal_uLong const until = iter == pMerged->extents.end() ? pMerged->pLastNode->GetIndex() + 1 : iter->pNode->GetIndex(); @@ -4251,7 +4253,8 @@ void AddRemoveFlysAnchoredToFrameStartingAtNode( if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst) { AddRemoveFlysForNode(rFrame, rTextNode, pSkipped, - rTable, pPage, pTmp->GetTextNode(), iter, iter); + rTable, pPage, pTmp->GetTextNode(), iter, iter, + pMerged->pFirstNode, pMerged->pLastNode); } } if (iter == pMerged->extents.end()) diff --git a/sw/source/core/txtnode/atrftn.cxx b/sw/source/core/txtnode/atrftn.cxx index 81afed72030b..18bf9c5d05f4 100644 --- a/sw/source/core/txtnode/atrftn.cxx +++ b/sw/source/core/txtnode/atrftn.cxx @@ -398,7 +398,7 @@ void SwTextFootnote::CopyFootnote( SwNodeIndex aEnd( *aStart.GetNode().EndOfSectionNode() ); sal_uLong nDestLen = aEnd.GetIndex() - aStart.GetIndex() - 1; - m_pTextNode->GetDoc()->GetDocumentContentOperationsManager().CopyWithFlyInFly( aRg, 0, aEnd ); + m_pTextNode->GetDoc()->GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aEnd); // in case the destination section was not empty, delete the old nodes // before: Src: SxxxE, Dst: SnE diff --git a/sw/source/core/undo/undobj.cxx b/sw/source/core/undo/undobj.cxx index ac36268b40d7..612ca3500bc3 100644 --- a/sw/source/core/undo/undobj.cxx +++ b/sw/source/core/undo/undobj.cxx @@ -1020,7 +1020,9 @@ void SwUndoSaveContent::DelContentIndex( const SwPosition& rMark, pHistory->Add( *static_cast<SwFlyFrameFormat *>(pFormat), nChainInsPos ); n = n >= rSpzArr.size() ? rSpzArr.size() : n+1; } - else if( !( DelContentType::CheckNoCntnt & nDelContentType ) ) + else if (!((DelContentType::CheckNoCntnt | + DelContentType::ExcludeAtCharFlyAtStartEnd) + & nDelContentType)) { if( *pStt <= *pAPos && *pAPos < *pEnd ) { @@ -1521,19 +1523,55 @@ OUString ShortenString(const OUString & rStr, sal_Int32 nLength, const OUString + rStr.copy(rStr.getLength() - nBackLen); } +static bool IsAtEndOfSection(SwPosition const& rAnchorPos) +{ + SwNodeIndex node(*rAnchorPos.nNode.GetNode().EndOfSectionNode()); + SwContentNode *const pNode(SwNodes::GoPrevious(&node)); + assert(pNode); + assert(rAnchorPos.nNode <= node); // last valid anchor pos is last content + return node == rAnchorPos.nNode && rAnchorPos.nContent == pNode->Len(); +} + +static bool IsAtStartOfSection(SwPosition const& rAnchorPos) +{ + SwNodes const& rNodes(rAnchorPos.nNode.GetNodes()); + SwNodeIndex node(*rAnchorPos.nNode.GetNode().StartOfSectionNode()); + SwContentNode *const pNode(rNodes.GoNext(&node)); + assert(pNode); + (void) pNode; + assert(node <= rAnchorPos.nNode); + return node == rAnchorPos.nNode && rAnchorPos.nContent == 0; +} + bool IsDestroyFrameAnchoredAtChar(SwPosition const & rAnchorPos, SwPosition const & rStart, SwPosition const & rEnd, DelContentType const nDelContentType) { - // Here we identified the objects to destroy: - // - anchored between start and end of the selection - // - anchored in start of the selection with "CheckNoContent" - // - anchored in start of sel. and the selection start at pos 0 - return (rAnchorPos.nNode < rEnd.nNode) - && ( (DelContentType::CheckNoCntnt & nDelContentType) - || (rStart.nNode < rAnchorPos.nNode) - || !rStart.nContent.GetIndex() - ); + // CheckNoCntnt means DelFullPara which is obvious to handle + if (DelContentType::CheckNoCntnt & nDelContentType) + { // exclude selection end node because it won't be deleted + return (rAnchorPos.nNode < rEnd.nNode) + && (rStart.nNode <= rAnchorPos.nNode); + } + + if (rAnchorPos.GetDoc()->IsInReading()) + { // FIXME hack for writerfilter RemoveLastParagraph(); can't test file format more specific? + return (rStart < rAnchorPos) && (rAnchorPos < rEnd); + } + + // in general, exclude the start and end position + return ((rStart < rAnchorPos) + || (rStart == rAnchorPos + && !(nDelContentType & DelContentType::ExcludeAtCharFlyAtStartEnd) + // special case: fully deleted node + && ((rStart.nNode != rEnd.nNode && rStart.nContent == 0) + || IsAtStartOfSection(rAnchorPos)))) + && ((rAnchorPos < rEnd) + || (rAnchorPos == rEnd + && !(nDelContentType & DelContentType::ExcludeAtCharFlyAtStartEnd) + // special case: fully deleted node + && ((rEnd.nNode != rStart.nNode && rEnd.nContent == rEnd.nNode.GetNode().GetTextNode()->Len()) + || IsAtEndOfSection(rAnchorPos)))); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/untblk.cxx b/sw/source/core/undo/untblk.cxx index 47465620b882..8b206e064873 100644 --- a/sw/source/core/undo/untblk.cxx +++ b/sw/source/core/undo/untblk.cxx @@ -32,6 +32,34 @@ #include <rolbck.hxx> #include <redline.hxx> +namespace sw { + +std::unique_ptr<std::vector<SwFrameFormat*>> +GetFlysAnchoredAt(SwDoc & rDoc, sal_uLong const nSttNode) +{ + std::unique_ptr<std::vector<SwFrameFormat*>> pFrameFormats; + const size_t nArrLen = rDoc.GetSpzFrameFormats()->size(); + for (size_t n = 0; n < nArrLen; ++n) + { + SwFrameFormat *const pFormat = (*rDoc.GetSpzFrameFormats())[n]; + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if (pAPos + && nSttNode == pAPos->nNode.GetIndex() + && ((pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA) + || (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR))) + { + if (!pFrameFormats) + pFrameFormats.reset( new std::vector<SwFrameFormat*> ); + pFrameFormats->push_back( pFormat ); + } + } + return pFrameFormats; +} + +} // namespace sw + +//note: parameter is SwPam just so we can init SwUndRng, the End is ignored! SwUndoInserts::SwUndoInserts( SwUndoId nUndoId, const SwPaM& rPam ) : SwUndo( nUndoId, rPam.GetDoc() ), SwUndRng( rPam ), pTextFormatColl( nullptr ), pLastNdColl(nullptr), @@ -53,22 +81,7 @@ SwUndoInserts::SwUndoInserts( SwUndoId nUndoId, const SwPaM& rPam ) // These flys will be saved in pFrameFormats array (only flys which exist BEFORE insertion!) // Then in SwUndoInserts::SetInsertRange the flys saved in pFrameFormats will NOT create Undos. // m_FlyUndos will only be filled with newly inserted flys. - - const size_t nArrLen = pDoc->GetSpzFrameFormats()->size(); - for( size_t n = 0; n < nArrLen; ++n ) - { - SwFrameFormat* pFormat = (*pDoc->GetSpzFrameFormats())[n]; - SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); - const SwPosition* pAPos = pAnchor->GetContentAnchor(); - if (pAPos && - (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA) && - nSttNode == pAPos->nNode.GetIndex() ) - { - if( !pFrameFormats ) - pFrameFormats.reset( new std::vector<SwFrameFormat*> ); - pFrameFormats->push_back( pFormat ); - } - } + pFrameFormats = sw::GetFlysAnchoredAt(*pDoc, nSttNode); } // consider Redline if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) @@ -124,10 +137,7 @@ void SwUndoInserts::SetInsertRange( const SwPaM& rPam, bool bScanFlys, { SwFrameFormat* pFormat = (*pDoc->GetSpzFrameFormats())[n]; SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); - SwPosition const*const pAPos = pAnchor->GetContentAnchor(); - if (pAPos && - (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA) && - (nSttNode == pAPos->nNode.GetIndex() || nEndNode == pAPos->nNode.GetIndex())) + if (IsCreateUndoForNewFly(*pAnchor, nSttNode, nEndNode)) { std::vector<SwFrameFormat*>::iterator it; if( !pFrameFormats || @@ -145,6 +155,29 @@ void SwUndoInserts::SetInsertRange( const SwPaM& rPam, bool bScanFlys, } } +/** This is not the same as IsDestroyFrameAnchoredAtChar() + and intentionally so: because the SwUndoInserts::UndoImpl() must remove + the flys at the start/end position that were inserted but not the ones + at the start/insert position that were already there; + handle all at-char flys at start/end node like this, even if they're + not *on* the start/end position, because it makes it easier to ensure + that the Undo/Redo run in inverse order. + */ +bool SwUndoInserts::IsCreateUndoForNewFly(SwFormatAnchor const& rAnchor, + sal_uLong const nStartNode, sal_uLong const nEndNode) +{ + assert(nStartNode <= nEndNode); + + // check all at-char flys at the start/end nodes: + // ExcludeAtCharFlyAtStartEnd will exclude them! + SwPosition const*const pAnchorPos = rAnchor.GetContentAnchor(); + return pAnchorPos != nullptr + && ( rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA + || rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + && ( nStartNode == pAnchorPos->nNode.GetIndex() + || nEndNode == pAnchorPos->nNode.GetIndex()); +} + SwUndoInserts::~SwUndoInserts() { if (m_pUndoNodeIndex) // delete also the section from UndoNodes array @@ -185,6 +218,8 @@ void SwUndoInserts::UndoImpl(::sw::UndoRedoContext & rContext) SwDoc& rDoc = rContext.GetDoc(); SwPaM& rPam = AddUndoRedoPaM(rContext); + nNdDiff = 0; + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any); @@ -204,14 +239,35 @@ void SwUndoInserts::UndoImpl(::sw::UndoRedoContext & rContext) } RemoveIdxFromRange(rPam, false); + SetPaM(rPam); + } + + // ... for consistency with the Insert File code in shellio.cxx, which + // creates separate SwUndoInsLayFormat for mysterious reasons, do this + // *before* anything else: + // after SetPaM but before MoveToUndoNds and DelContentIndex. + // note: there isn't an order dep wrt. initial Copy action because Undo + // overwrites the indexes but there is wrt. Redo because that uses the + // indexes + if (!m_FlyUndos.empty()) + { + sal_uLong nTmp = rPam.GetPoint()->nNode.GetIndex(); + for (size_t n = m_FlyUndos.size(); 0 < n; --n) + { + m_FlyUndos[ n-1 ]->UndoImpl(rContext); + } + nNdDiff += nTmp - rPam.GetPoint()->nNode.GetIndex(); + } + if (nSttNode != nEndNode || nSttContent != nEndContent) + { // are there Footnotes or ContentFlyFrames in text? nSetPos = pHistory->Count(); - nNdDiff = rPam.GetMark()->nNode.GetIndex(); - DelContentIndex(*rPam.GetMark(), *rPam.GetPoint()); - nNdDiff -= rPam.GetMark()->nNode.GetIndex(); - + sal_uLong nTmp = rPam.GetMark()->nNode.GetIndex(); + DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(), + DelContentType::AllMask|DelContentType::ExcludeAtCharFlyAtStartEnd); + nNdDiff += nTmp - rPam.GetMark()->nNode.GetIndex(); if( *rPam.GetPoint() != *rPam.GetMark() ) { m_pUndoNodeIndex.reset( @@ -223,16 +279,6 @@ void SwUndoInserts::UndoImpl(::sw::UndoRedoContext & rContext) } } - if (!m_FlyUndos.empty()) - { - sal_uLong nTmp = rPam.GetPoint()->nNode.GetIndex(); - for (size_t n = m_FlyUndos.size(); 0 < n; --n) - { - m_FlyUndos[ n-1 ]->UndoImpl(rContext); - } - nNdDiff += nTmp - rPam.GetPoint()->nNode.GetIndex(); - } - SwNodeIndex& rIdx = rPam.GetPoint()->nNode; SwTextNode* pTextNode = rIdx.GetNode().GetTextNode(); if( pTextNode ) @@ -296,6 +342,9 @@ void SwUndoInserts::RedoImpl(::sw::UndoRedoContext & rContext) // retrieve start position for rollback if( ( nSttNode != nEndNode || nSttContent != nEndContent ) && m_pUndoNodeIndex) { + auto const pFlysAtInsPos(sw::GetFlysAnchoredAt(*pDoc, + rPam.GetPoint()->nNode.GetIndex())); + const bool bMvBkwrd = MovePtBackward(rPam); // re-insert content again (first detach m_pUndoNodeIndex!) @@ -305,6 +354,22 @@ void SwUndoInserts::RedoImpl(::sw::UndoRedoContext & rContext) if( bSttWasTextNd ) MovePtForward(rPam, bMvBkwrd); rPam.Exchange(); + + // at-char anchors post SplitNode are on index 0 of 2nd node and will + // remain there - move them back to the start (end would also work?) + if (pFlysAtInsPos) + { + for (SwFrameFormat * pFly : *pFlysAtInsPos) + { + SwFormatAnchor const*const pAnchor = &pFly->GetAnchor(); + if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + SwFormatAnchor anchor(*pAnchor); + anchor.SetAnchor( rPam.GetMark() ); + pFly->SetFormatAttr(anchor); + } + } + } } if (pDoc->GetTextFormatColls()->IsAlive(pTextFormatColl)) @@ -323,6 +388,10 @@ void SwUndoInserts::RedoImpl(::sw::UndoRedoContext & rContext) pTextNd->ChgFormatColl( pLastNdColl ); } + // tdf#108124 the SwHistoryChangeFlyAnchor/SwHistoryFlyCnt must run before + // m_FlyUndos as they were created by DelContentIndex() + pHistory->Rollback( pDoc, nSetPos ); + // tdf#108124 (10/25/2017) // During UNDO we call SwUndoInsLayFormat::UndoImpl in reverse order, // firstly for m_FlyUndos[ m_FlyUndos.size()-1 ], etc. @@ -336,8 +405,6 @@ void SwUndoInserts::RedoImpl(::sw::UndoRedoContext & rContext) m_FlyUndos[n]->RedoImpl(rContext); } - pHistory->Rollback( pDoc, nSetPos ); - if( pRedlData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) { RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); diff --git a/sw/source/filter/basflt/shellio.cxx b/sw/source/filter/basflt/shellio.cxx index 6fc17d4f9858..66c9d1d0ea7d 100644 --- a/sw/source/filter/basflt/shellio.cxx +++ b/sw/source/filter/basflt/shellio.cxx @@ -240,27 +240,11 @@ ErrCode SwReader::Read( const Reader& rOptions ) // ok, here IsAlive is a misnomer... if (!aFlyFrameArr.IsAlive(pFrameFormat)) { - SwPosition const*const pFrameAnchor( - rAnchor.GetContentAnchor()); if ( (RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()) - || ( pFrameAnchor - && ( ( (RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) - && ( (pUndoPam->GetPoint()->nNode == - pFrameAnchor->nNode) - || (pUndoPam->GetMark()->nNode == - pFrameAnchor->nNode) - ) - ) - // #i97570# also check frames anchored AT char - || ( (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) - && !IsDestroyFrameAnchoredAtChar( - *pFrameAnchor, - *pUndoPam->GetPoint(), - *pUndoPam->GetMark()) - ) - ) - ) - ) + // TODO: why is this not handled via SetInsertRange? + || SwUndoInserts::IsCreateUndoForNewFly(rAnchor, + pUndoPam->GetPoint()->nNode.GetIndex(), + pUndoPam->GetMark()->nNode.GetIndex())) { if( bChkHeaderFooter && (RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) && |