diff --git a/sw/inc/crsrsh.hxx b/sw/inc/crsrsh.hxx index d21dac474ced..288bd88f202c 100644 --- a/sw/inc/crsrsh.hxx +++ b/sw/inc/crsrsh.hxx @@ -664,7 +664,7 @@ public: // select a table row, column or box (based on the current cursor) bool SelTableRow() { return SelTableRowOrCol( true ); } bool SelTableCol() { return SelTableRowOrCol( false ); } - bool SelTableBox(); + SW_DLLPUBLIC bool SelTableBox(); SW_DLLPUBLIC bool SelTable(); @@ -870,7 +870,7 @@ public: bool CheckTableBoxContent( const SwPosition* pPos = nullptr ); void SaveTableBoxContent( const SwPosition* pPos = nullptr ); void ClearTableBoxContent(); - bool EndAllTableBoxEdit(); + SW_DLLPUBLIC bool EndAllTableBoxEdit(); void SetSelTableCells( bool bFlag ) { m_bSelTableCells = bFlag; } bool IsSelTableCells() const { return m_bSelTableCells; } diff --git a/sw/qa/core/undo/data/tdf169795.odt b/sw/qa/core/undo/data/tdf169795.odt new file mode 100755 index 000000000000..c675f82c7ed5 Binary files /dev/null and b/sw/qa/core/undo/data/tdf169795.odt differ diff --git a/sw/qa/core/undo/undo.cxx b/sw/qa/core/undo/undo.cxx index 339fed31c1f2..5355eb4e9249 100644 --- a/sw/qa/core/undo/undo.cxx +++ b/sw/qa/core/undo/undo.cxx @@ -25,6 +25,17 @@ #include #include #include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include /// Covers sw/source/core/undo/ fixes. class SwCoreUndoTest : public SwModelTestBase @@ -105,6 +116,175 @@ CPPUNIT_TEST_FIXTURE(SwCoreUndoTest, testTableCopyRedline) pWrtShell->Undo(); } +namespace +{ +/// Put the view cursor into the given cell of the given table. +void lcl_GotoCell(const uno::Reference& xComponent, SwXTextDocument* pTextDoc, + const OUString& rTable, const OUString& rCell) +{ + uno::Reference xSupplier(xComponent, uno::UNO_QUERY_THROW); + uno::Reference xTable(xSupplier->getTextTables()->getByName(rTable), + uno::UNO_QUERY_THROW); + uno::Reference xCell(xTable->getCellByName(rCell), uno::UNO_QUERY_THROW); + uno::Reference xCursorSupplier(pTextDoc->getCurrentController(), + uno::UNO_QUERY_THROW); + xCursorSupplier->getViewCursor()->gotoRange(xCell->getStart(), /*bExpand=*/false); +} + +/// The text of the given cell of the given table. +OUString lcl_GetCellText(const uno::Reference& xComponent, const OUString& rTable, + const OUString& rCell) +{ + uno::Reference xSupplier(xComponent, uno::UNO_QUERY_THROW); + uno::Reference xTable(xSupplier->getTextTables()->getByName(rTable), + uno::UNO_QUERY_THROW); + uno::Reference xCell(xTable->getCellByName(rCell), uno::UNO_QUERY_THROW); + return xCell->getString(); +} + +/// The formula of the given cell of the given table, empty when it holds none. +OUString lcl_GetCellFormula(const uno::Reference& xComponent, + const OUString& rTable, const OUString& rCell) +{ + uno::Reference xSupplier(xComponent, uno::UNO_QUERY_THROW); + uno::Reference xTable(xSupplier->getTextTables()->getByName(rTable), + uno::UNO_QUERY_THROW); + uno::Reference xCell(xTable->getCellByName(rCell)); + CPPUNIT_ASSERT(xCell.is()); + return xCell->getFormula(); +} + +/// The tracked changes of the document, as a ":" list. +OUString lcl_GetRedlines(SwDoc* pDoc) +{ + OUString aRet; + const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for (SwRedlineTable::size_type i = 0; i < rTable.size(); ++i) + { + if (!aRet.isEmpty()) + aRet += " "; + aRet += SwRedlineTypeToOUString(rTable[i]->GetType()) + ":" + rTable[i]->GetText(); + } + return aRet; +} + +/// Every cursor position has to stay inside the text of the node it points at. +void lcl_AssertCursorInText(SwWrtShell* pWrtShell) +{ + for (const SwPaM& rPaM : pWrtShell->GetCursor()->GetRingContainer()) + { + for (const SwPosition* pPos : { rPaM.GetPoint(), rPaM.GetMark() }) + { + const SwContentNode* pContentNode = pPos->GetNode().GetContentNode(); + if (!pContentNode) + continue; + CPPUNIT_ASSERT_LESSEQUAL(pContentNode->Len(), pPos->GetContentIndex()); + } + } +} +} + +CPPUNIT_TEST_FIXTURE(SwCoreUndoTest, testTdf169795CopyCellWithFormula) +{ + // Given a document with two tables and change recording enabled, where the cell to copy + // holds a formula and the cell to copy it over does not: + createSwDoc("tdf169795.odt"); + SwDocShell* pDocShell = getSwDocShell(); + SwWrtShell* pWrtShell = pDocShell->GetWrtShell(); + SwXTextDocument* pTextDoc = getSwTextDoc(); + SwDoc* pDoc = pWrtShell->GetDoc(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::On | RedlineFlags::ShowInsert + | RedlineFlags::ShowDelete); + CPPUNIT_ASSERT_EQUAL(OUString(), lcl_GetCellFormula(mxComponent, u"Table1"_ustr, u"D2"_ustr)); + + // When copying that whole cell, "$100.00", over "$10.00": + lcl_GotoCell(mxComponent, pTextDoc, u"Table2"_ustr, u"D3"_ustr); + pWrtShell->SelTableBox(); + rtl::Reference pTransfer = new SwTransferable(*pWrtShell); + pTransfer->Copy(); + TransferableDataHelper aHelper(pTransfer); + lcl_GotoCell(mxComponent, pTextDoc, u"Table1"_ustr, u"D2"_ustr); + pWrtShell->SelTableBox(); + SwTransferable::Paste(*pWrtShell, aHelper); + + // Then no change is recorded over the cell: what it shows is worked out from the formula in + // its new place, not text that somebody wrote, so there is nothing to accept or reject. + // Without the accompanying fix in place, the copy was recorded as a change over text that + // the recalculation then rewrote, and the records left over that text described text that + // was no longer there. + CPPUNIT_ASSERT_EQUAL(OUString(), lcl_GetRedlines(pDoc)); + CPPUNIT_ASSERT(!lcl_GetCellFormula(mxComponent, u"Table1"_ustr, u"D2"_ustr).isEmpty()); + CPPUNIT_ASSERT_EQUAL(u"$10.00"_ustr, lcl_GetCellText(mxComponent, u"Table1"_ustr, u"D2"_ustr)); + + // And undo takes the formula away again, leaving the old value behind. + pWrtShell->Undo(); + CPPUNIT_ASSERT_EQUAL(OUString(), lcl_GetRedlines(pDoc)); + CPPUNIT_ASSERT_EQUAL(OUString(), lcl_GetCellFormula(mxComponent, u"Table1"_ustr, u"D2"_ustr)); + CPPUNIT_ASSERT_EQUAL(u"$10.00"_ustr, lcl_GetCellText(mxComponent, u"Table1"_ustr, u"D2"_ustr)); + + // The cursor has to stay inside the text it points at. Without the fix, undo left it on the + // range of the pasted text, which the undo removed again, so it pointed past the end of the + // cell text: content index 11 of a 6 character long node. + lcl_AssertCursorInText(pWrtShell); + + // ... and counting words over that selection must not run out of the text either; this is + // what the status bar does, and what crashed in SwScanner. + SwDocStat aDocStat; + pWrtShell->CountWords(aDocStat); + + // Redo puts the formula back, again without recording a change, and again with a cursor that + // describes something. Without the fix, redo went down the route the copy had not taken. + pWrtShell->Redo(); + CPPUNIT_ASSERT_EQUAL(OUString(), lcl_GetRedlines(pDoc)); + CPPUNIT_ASSERT(!lcl_GetCellFormula(mxComponent, u"Table1"_ustr, u"D2"_ustr).isEmpty()); + lcl_AssertCursorInText(pWrtShell); + pWrtShell->CountWords(aDocStat); +} + +CPPUNIT_TEST_FIXTURE(SwCoreUndoTest, testTdf169795NumFormatCellPasteKeepsTrackedChanges) +{ + // Given a document with number cells and change recording enabled: + createSwDoc("tdf169795.odt"); + SwDocShell* pDocShell = getSwDocShell(); + SwWrtShell* pWrtShell = pDocShell->GetWrtShell(); + SwXTextDocument* pTextDoc = getSwTextDoc(); + SwDoc* pDoc = pWrtShell->GetDoc(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::On | RedlineFlags::ShowInsert + | RedlineFlags::ShowDelete); + + // When copying one currency cell over another one: + lcl_GotoCell(mxComponent, pTextDoc, u"Table1"_ustr, u"C3"_ustr); + pWrtShell->SelTableBox(); + rtl::Reference pTransfer = new SwTransferable(*pWrtShell); + pTransfer->Copy(); + TransferableDataHelper aHelper(pTransfer); + lcl_GotoCell(mxComponent, pTextDoc, u"Table1"_ustr, u"C2"_ustr); + pWrtShell->SelTableBox(); + SwTransferable::Paste(*pWrtShell, aHelper); + + // Then the old value has to be kept as a deletion. + // Without the accompanying fix in place, the number format of the box regenerated the text + // of the cell right after the copy, which dropped the deletion: only the insertion was left, + // and the old value was gone for good. + OUString aExpected(u"Insert:$2.00 Delete:$1.00"_ustr); + CPPUNIT_ASSERT_EQUAL(aExpected, lcl_GetRedlines(pDoc)); + + // Leaving the cell must not drop it either, that is where the cell is checked against its + // number format. + pWrtShell->EndAllTableBoxEdit(); + CPPUNIT_ASSERT_EQUAL(aExpected, lcl_GetRedlines(pDoc)); + + // And undo and redo have to be symmetric: undo leaves the old value alone, + pWrtShell->Undo(); + CPPUNIT_ASSERT_EQUAL(OUString(), lcl_GetRedlines(pDoc)); + CPPUNIT_ASSERT_EQUAL(u"$1.00"_ustr, lcl_GetCellText(mxComponent, u"Table1"_ustr, u"C2"_ustr)); + + // ... and redo puts the very same pair of changes back. Without the fix, redo produced two + // empty redlines instead, which piled up over further undo and redo rounds. + pWrtShell->Redo(); + CPPUNIT_ASSERT_EQUAL(aExpected, lcl_GetRedlines(pDoc)); +} + CPPUNIT_TEST_FIXTURE(SwCoreUndoTest, testImagePropsCreateUndoAndModifyDoc) { createSwDoc("image-as-character.odt"); diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx index 119e8ab3421a..2cd9507cbd67 100644 --- a/sw/source/core/crsr/crsrsh.cxx +++ b/sw/source/core/crsr/crsrsh.cxx @@ -4276,6 +4276,25 @@ void SwCursorShell::ClearUpCursors() pStartCursor->DeleteMark(); bChanged = true; } + + // sw_PosOk only asks whether the position is in a content node, so a + // content index pointing past the end of the text survives it. + // It should not happen, but still happened when an undo left it behind when + // it rebuilt the text it had selected. + // Keep the offsets inside the node, so that users of the selection cannot run + // out of bounds. + for (SwPaM& rPaM : pStartCursor->GetRingContainer()) + { + for (SwPosition* pPos : { rPaM.GetPoint(), rPaM.GetMark() }) + { + const SwContentNode* pContentNode = pPos->GetNode().GetContentNode(); + if (pContentNode && pPos->GetContentIndex() > pContentNode->Len()) + { + pPos->SetContent(pContentNode->Len()); + bChanged = true; + } + } + } if (pStartCursor->GetPoint()->GetNode().IsTableNode()) { // tdf#106959: When cursor points to start of a table, the proper content diff --git a/sw/source/core/doc/tblcpy.cxx b/sw/source/core/doc/tblcpy.cxx index 986da9801782..3ee04912120e 100644 --- a/sw/source/core/doc/tblcpy.cxx +++ b/sw/source/core/doc/tblcpy.cxx @@ -505,10 +505,19 @@ static void lcl_CpyBox( const SwTable& rCpyTable, const SwTableBox* pCpyBox, pDstBox->GetSttNd()->EndOfSectionIndex() - pDstBox->GetSttIdx() ); - if( pUndo ) - pUndo->AddBoxBefore( *pDstBox, bDelContent ); + // Do not record (track) changes if the cell holds a formula. + // The text that arrives with the copy will be recalculated based on the formula, + // and that would eliminate the tracked change from the cell anyway. + // TODO: if we want tracked changes to work on it, we should create a new type of + // tracked change that track the formula changes, not the cell values changes. + const bool bTrackChanges = !pCpyBox + || SfxItemState::SET + != pCpyBox->GetFrameFormat()->GetItemState( RES_BOXATR_FORMULA, false ); - bool bUndoRedline = pUndo && rDoc.getIDocumentRedlineAccess().IsRedlineOn(); + if( pUndo ) + pUndo->AddBoxBefore( *pDstBox, bDelContent, bTrackChanges ); + + bool bUndoRedline = pUndo && bTrackChanges && rDoc.getIDocumentRedlineAccess().IsRedlineOn(); ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); SwNodeIndex aSavePos( aInsIdx, -1 ); @@ -574,7 +583,7 @@ static void lcl_CpyBox( const SwTable& rCpyTable, const SwTableBox* pCpyBox, //b6341295: Table copy redlining will be managed by AddBoxAfter() if( pUndo ) - pUndo->AddBoxAfter( *pDstBox, aInsIdx, bDelContent ); + pUndo->AddBoxAfter( *pDstBox, aInsIdx, bDelContent, bTrackChanges ); // heading SwTextNode *const pTextNd = aSavePos.GetNode().GetTextNode(); diff --git a/sw/source/core/inc/UndoTable.hxx b/sw/source/core/inc/UndoTable.hxx index 39e2adbcd01c..a62569036354 100644 --- a/sw/source/core/inc/UndoTable.hxx +++ b/sw/source/core/inc/UndoTable.hxx @@ -292,9 +292,11 @@ public: virtual void UndoImpl( ::sw::UndoRedoContext & ) override; virtual void RedoImpl( ::sw::UndoRedoContext & ) override; - void AddBoxBefore( const SwTableBox& rBox, bool bDelContent ); + /// bTrackChanges is false for a box that is copied without recording it as a change, even + /// though changes are being recorded: see lcl_CpyBox(). + void AddBoxBefore( const SwTableBox& rBox, bool bDelContent, bool bTrackChanges ); void AddBoxAfter( const SwTableBox& rBox, const SwNodeIndex& rIdx, - bool bDelContent ); + bool bDelContent, bool bTrackChanges ); bool IsEmpty() const; bool InsertRow( SwTable& rTable, const SwSelBoxes& rBoxes, sal_uInt16 nCnt ); diff --git a/sw/source/core/table/swtable.cxx b/sw/source/core/table/swtable.cxx index 5eb4018e007e..c5ad983470e6 100644 --- a/sw/source/core/table/swtable.cxx +++ b/sw/source/core/table/swtable.cxx @@ -2431,7 +2431,16 @@ void ChgTextToNum( SwTableBox& rBox, const OUString& rText, const Color* pCol, } rBox.SetSaveNumFormatColor( pCol ? *pCol : std::optional() ); - if( pTNd->GetText() != rText ) + // What the cell shows is its text without what a tracked deletion took out of it, which is + // also the text HasNumContent() and IsNumberChanged() work the value out from. Ask whether + // that already says what the format wants: if it does there is nothing to do, and the + // changes recorded in the cell are left alone. + // + // If it does not, the text is rewritten as a whole below, which loses the changes recorded + // over it. That is coarse, but a number format is free to rewrite the text into anything - + // a number can come out as words, or grow a thousands separator that shifts every position + // after it - so there is no general way to carry a set of changes across a reformat. + if( pTNd->GetRedlineText() != rText ) { // Exchange text. Bugfix to keep Tabs (front and back!) and annotations (inword comment anchors) const OUString& rOrig = pTNd->GetText(); @@ -2475,12 +2484,18 @@ void ChgTextToNum( SwTableBox& rBox, const OUString& rText, const Color* pCol, while( nCommentPos > aIdx.GetIndex() && nCommentPos == nEndPos ); } + const sal_Int32 nInsStt = aIdx.GetIndex(); pTNd->EraseText( aIdx, n, SwInsertFlags::EMPTYEXPAND ); pTNd->InsertText( rText, aIdx, SwInsertFlags::EMPTYEXPAND ); - if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + // The text of a box with a formula is worked out from the formula and is rewritten + // whenever the table is recalculated, by a change to some other box as much as by one + // here. Nobody wrote it, so do not record it as an insertion. + const bool bFormulaBox = SfxItemState::SET + == rBox.GetFrameFormat()->GetItemState( RES_BOXATR_FORMULA, false ); + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() && !bFormulaBox ) { - SwPaM aTemp(*pTNd, 0, *pTNd, rText.getLength()); + SwPaM aTemp(*pTNd, nInsStt, *pTNd, nInsStt + rText.getLength()); rDoc.getIDocumentRedlineAccess().AppendRedline(new SwRangeRedline(RedlineType::Insert, aTemp), true); } } diff --git a/sw/source/core/txtnode/txtedt.cxx b/sw/source/core/txtnode/txtedt.cxx index 9bcdfddc2131..46953d1aaa5e 100644 --- a/sw/source/core/txtnode/txtedt.cxx +++ b/sw/source/core/txtnode/txtedt.cxx @@ -18,6 +18,7 @@ */ #include +#include #include #include @@ -751,7 +752,10 @@ SwScanner::SwScanner(std::function aGe , m_bClip(bClp) { m_nStartPos = m_nBegin = nStart; - m_nEndPos = nEnd; + // The range comes from a selection, which can be stale - e.g. an undo that + // rebuilt the text leaves the cursor describing the old, longer text. Keep + // the scan inside the string; + m_nEndPos = std::min(nEnd, m_aPreDashReplacementText.getLength()); //MSWord f.e has special emdash and endash behaviour in that they break //words for the purposes of word counting, while a hyphen etc. doesn't. diff --git a/sw/source/core/undo/untbl.cxx b/sw/source/core/undo/untbl.cxx index 3307b43b07bd..8e9f03e49b5c 100644 --- a/sw/source/core/undo/untbl.cxx +++ b/sw/source/core/undo/untbl.cxx @@ -19,6 +19,8 @@ #include +#include + #include #include #include @@ -91,6 +93,10 @@ struct UndoTableCpyTable_Entry // Was the last paragraph of the new and the first paragraph of the old content joined? bool bJoin; // For redlining only + // Was this box copied as a change? A box that holds a formula is not, see lcl_CpyBox(), + // and then undo and redo have to take the same route the copy took. + bool bTrackChanges; + explicit UndoTableCpyTable_Entry( const SwTableBox& rBox ); void dumpAsXml(xmlTextWriterPtr pWriter) const; @@ -2360,7 +2366,7 @@ void SwUndoTableNumFormat::SetBox( const SwTableBox& rBox ) UndoTableCpyTable_Entry::UndoTableCpyTable_Entry( const SwTableBox& rBox ) : nBoxIdx( rBox.GetSttIdx() ), nOffset( 0 ), - bJoin( false ) + bJoin( false ), bTrackChanges( true ) { } @@ -2397,6 +2403,11 @@ void UndoTableCpyTable_Entry::dumpAsXml(xmlTextWriterPtr pWriter) const BAD_CAST(OString::boolean(bJoin).getStr())); (void)xmlTextWriterEndElement(pWriter); + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("bTrackChanges")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(bTrackChanges).getStr())); + (void)xmlTextWriterEndElement(pWriter); + (void)xmlTextWriterEndElement(pWriter); } @@ -2411,6 +2422,39 @@ SwUndoTableCpyTable::~SwUndoTableCpyTable() m_pInsRowUndo.reset(); } +namespace +{ +// Move cursors off the temporary node at the start of the box, onto the content that stays. +// +// UndoImpl() and RedoImpl() park cursors on that node while they hand the content of the box to +// an undo object, the same way AddBoxBefore() does, and the node is deleted right after, so the +// cursors have to come off it first. This is what AddBoxAfter() does at the end of a copy. +void lcl_MoveCursorsOffTempNode(const SwTableBox & rBox) +{ + SwPaM aPam(SwNodeIndex(*rBox.GetSttNd(), 1)); + if (!aPam.GetPoint()->GetNode().IsTextNode()) + return; + aPam.SetMark(); + aPam.Move(fnMoveForward, GoInContent); + ::PaMCorrAbs(aPam, *aPam.GetPoint()); +} + +// Put the cursor at the start of the content of the box that starts at nBoxStart. +void lcl_SetCursorToBoxContent(::sw::UndoRedoContext & rContext, + const SwTableNode *const pTableNd, SwNodeOffset const nBoxStart) +{ + if (!pTableNd) + return; + const SwTableBox *const pBox = pTableNd->GetTable().GetTableBox(nBoxStart); + if (!pBox || !pBox->GetSttNd()) + return; + SwPaM & rPaM(rContext.GetCursorSupplier().CreateNewShellCursor()); + rPaM.DeleteMark(); + rPaM.GetPoint()->Assign(*pBox->GetSttNd()); + rPaM.Move(fnMoveForward, GoInContent); +} +} + void SwUndoTableCpyTable::UndoImpl(::sw::UndoRedoContext & rContext) { SwDoc & rDoc = rContext.GetDoc(); @@ -2436,14 +2480,22 @@ void SwUndoTableCpyTable::UndoImpl(::sw::UndoRedoContext & rContext) SwTableBox& rBox = *pBox; SwNodeIndex aInsIdx( *rBox.GetSttNd(), 1 ); - rDoc.GetNodes().MakeTextNode( aInsIdx.GetNode(), rDoc.GetDfltTextFormatColl() ); + SwTextNode *const pNewNode( + rDoc.GetNodes().MakeTextNode( aInsIdx.GetNode(), rDoc.GetDfltTextFormatColl() )); // b62341295: Redline for copying tables const SwNode *pEndNode = rBox.GetSttNd()->EndOfSectionNode(); SwPaM aPam( aInsIdx.GetNode(), *pEndNode ); std::unique_ptr pUndo; - if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) ) + { // move cursors to the new node which precedes aPam, the way AddBoxBefore() does: + // the content below is about to be handed to an undo object, and a cursor left in it + // still points there once that object lets the nodes go. + SwPosition const pos(*pNewNode, 0); + ::PaMCorrAbs(aPam, pos); + } + + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) && pEntry->bTrackChanges ) { bool bDeleteCompleteParagraph = false; bool bShiftPam = false; @@ -2465,8 +2517,14 @@ void SwUndoTableCpyTable::UndoImpl(::sw::UndoRedoContext & rContext) SwTextNode *pText = aTmpIdx.GetNode().GetTextNode(); if( pText ) { + // The content start was recorded when the copy was done. The text of a + // box can be rebuilt after that, by the number format of the box + // regenerating it from the value, and that leaves the recorded offset + // describing text that is no longer there. Keep it inside the text, the + // same way the redline table clamps its own stale positions. aPam.GetPoint()->Assign(*pText, - pUndoRedlineDelete->ContentStart() ); + std::min(pUndoRedlineDelete->ContentStart(), + pText->GetText().getLength()) ); } else aPam.GetPoint()->Assign( aTmpIdx ); @@ -2510,6 +2568,7 @@ void SwUndoTableCpyTable::UndoImpl(::sw::UndoRedoContext & rContext) pEntry->pUndo = std::move(pUndo); aInsIdx = rBox.GetSttIdx() + 1; + lcl_MoveCursorsOffTempNode( rBox ); rDoc.GetNodes().Delete( aInsIdx ); SfxItemSet aTmpSet(SfxItemSet::makeFixedSfxItemSetUndoImpl(rContext); } + + if (!m_vArr.empty()) + lcl_SetCursorToBoxContent(rContext, pTableNd, m_vArr[0]->nBoxIdx + m_vArr[0]->nOffset); + DEBUG_REDLINE( rDoc ) } @@ -2570,15 +2633,24 @@ void SwUndoTableCpyTable::RedoImpl(::sw::UndoRedoContext & rContext) SwNodeIndex aInsIdx( *rBox.GetSttNd(), 1 ); // b62341295: Redline for copying tables - Start. - rDoc.GetNodes().MakeTextNode( aInsIdx.GetNode(), rDoc.GetDfltTextFormatColl() ); + SwTextNode *const pNewNode( + rDoc.GetNodes().MakeTextNode( aInsIdx.GetNode(), rDoc.GetDfltTextFormatColl() )); SwPaM aPam( aInsIdx.GetNode(), *rBox.GetSttNd()->EndOfSectionNode()); - std::unique_ptr pUndo(IDocumentRedlineAccess::IsRedlineOn(GetRedlineFlags()) + const bool bRedlineBox = IDocumentRedlineAccess::IsRedlineOn(GetRedlineFlags()) + && pEntry->bTrackChanges; + if( !bRedlineBox ) + { // as in UndoImpl() above: take cursors out of the content that the undo object below + // is about to take over + SwPosition const pos(*pNewNode, 0); + ::PaMCorrAbs(aPam, pos); + } + std::unique_ptr pUndo(bRedlineBox ? nullptr : std::make_unique(aPam, SwDeleteFlags::Default, true)); if( pEntry->pUndo ) { pEntry->pUndo->UndoImpl(rContext); - if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) ) + if( bRedlineBox ) { // PrepareRedline has to be called with the beginning of the old content // When new and old content has been joined, the rIter.pAktPam has been set @@ -2603,6 +2675,7 @@ void SwUndoTableCpyTable::RedoImpl(::sw::UndoRedoContext & rContext) // b62341295: Redline for copying tables - End. aInsIdx = rBox.GetSttIdx() + 1; + lcl_MoveCursorsOffTempNode( rBox ); rDoc.GetNodes().Delete( aInsIdx ); SfxItemSet aTmpSet(SfxItemSet::makeFixedSfxItemSetnOffset = rBox.GetSttIdx() - pEntry->nBoxIdx; } + + if (!m_vArr.empty()) + lcl_SetCursorToBoxContent(rContext, pTableNd, m_vArr[0]->nBoxIdx + m_vArr[0]->nOffset); + DEBUG_REDLINE( rDoc ) } -void SwUndoTableCpyTable::AddBoxBefore( const SwTableBox& rBox, bool bDelContent ) +void SwUndoTableCpyTable::AddBoxBefore( const SwTableBox& rBox, bool bDelContent, + bool bTrackChanges ) { if (!m_vArr.empty() && !bDelContent) return; UndoTableCpyTable_Entry* pEntry = new UndoTableCpyTable_Entry( rBox ); + pEntry->bTrackChanges = bTrackChanges; m_vArr.push_back(std::unique_ptr(pEntry)); SwDoc& rDoc = rBox.GetFrameFormat()->GetDoc(); @@ -2649,7 +2728,7 @@ void SwUndoTableCpyTable::AddBoxBefore( const SwTableBox& rBox, bool bDelContent SwTextNode *const pNewNode(rDoc.GetNodes().MakeTextNode(aInsIdx.GetNode(), rDoc.GetDfltTextFormatColl())); SwPaM aPam( aInsIdx.GetNode(), *rBox.GetSttNd()->EndOfSectionNode() ); - if( !rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + if( !bTrackChanges || !rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) { { // move cursors to new node which precedes aPam SwPosition const pos(*pNewNode, 0); @@ -2670,7 +2749,8 @@ void SwUndoTableCpyTable::AddBoxBefore( const SwTableBox& rBox, bool bDelContent DEBUG_REDLINE( rDoc ) } -void SwUndoTableCpyTable::AddBoxAfter( const SwTableBox& rBox, const SwNodeIndex& rIdx, bool bDelContent ) +void SwUndoTableCpyTable::AddBoxAfter( const SwTableBox& rBox, const SwNodeIndex& rIdx, + bool bDelContent, bool bTrackChanges ) { UndoTableCpyTable_Entry *const pEntry = m_vArr.back().get(); @@ -2687,7 +2767,7 @@ void SwUndoTableCpyTable::AddBoxAfter( const SwTableBox& rBox, const SwNodeIndex pam.Move(fnMoveForward, GoInContent); ::PaMCorrAbs(pam, *pam.GetPoint()); } - if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + if( bTrackChanges && rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) { SwPosition aTmpPos( rIdx ); pEntry->pUndo = PrepareRedline( rDoc, rBox, aTmpPos, pEntry->bJoin, false );