diff options
author | Attila Szűcs <attila.szucs@collabora.com> | 2023-10-17 09:31:22 +0200 |
---|---|---|
committer | Caolán McNamara <caolan.mcnamara@collabora.com> | 2023-10-29 19:30:43 +0100 |
commit | e4fb4937b3f75ce3544f8de354ed92f7dd314511 (patch) | |
tree | 680dc5473643210c9afbd467c446cd2ea59f8477 /sw | |
parent | 81f36caea6acd3042d42625940a9396305f5569c (diff) |
tdf#157663 SW: Tracked change improve move
Made accept/reject handle move redlines other pair, (moveto-movefrom)
and handle the whole move redline, even if it is split into small pieces
that separated from each other.
Added unique ID to every move redline to help find their other parts.
This move ID is generated in case of:
move recognition
moveing a paragraph. (directly create move redline with unique id without
calling the recognition it is faster and more stable)
(there are other cases that could be improved to not use recognition,
but generate ID directly, like moveing selected partial text with mouse)
Implemented the odt export/import of this move ID.
it is a tag like this: "<loext:move-id>4</loext:move-id>"
next to creator/date
Improved the docx import to generate this move ID, so move redlines can
find their other parts
(Not changed Docx export... it works a bit, but far from perfect)
Improved move reckognition:
It can find them even if they are split into multiple parts differently.
(like "ab"+"cd" == "a"+"bcd")
Disabled this because of probably performance issue.
made a complex unit test for it.
Note: Left the move recognition on every place, to avoid as much
regressions as possible.. but in the future, we may can disable it
in some cases.
Note2: We will have to keep move recognitnion, because there are documents
from past, saved without any move informations in the file, and users
expect to see move redlines there. (generated by the recognition.)
Change-Id: If968d4235b676c5e538cfaf4187a4482a86eae9f
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/157740
Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158611
Tested-by: Jenkins
Diffstat (limited to 'sw')
23 files changed, 583 insertions, 174 deletions
diff --git a/sw/inc/IDocumentContentOperations.hxx b/sw/inc/IDocumentContentOperations.hxx index 43e2c8492d51..eafc586886c2 100644 --- a/sw/inc/IDocumentContentOperations.hxx +++ b/sw/inc/IDocumentContentOperations.hxx @@ -132,7 +132,7 @@ public: rPam. If false, then no such check will be performed, and it is assumed that the caller took care of verifying this constraint already. */ - virtual bool CopyRange(SwPaM& rPam, SwPosition& rPos, SwCopyFlags flags) const = 0; + virtual bool CopyRange(SwPaM& rPam, SwPosition& rPos, SwCopyFlags flags, sal_uInt32 nMovedID = 0) const = 0; /** Delete section containing the node. */ diff --git a/sw/inc/crsrsh.hxx b/sw/inc/crsrsh.hxx index fd28607c5e32..20636648c794 100644 --- a/sw/inc/crsrsh.hxx +++ b/sw/inc/crsrsh.hxx @@ -166,7 +166,7 @@ public: READONLY = (1 << 3) ///< make visible in spite of Readonly }; - SAL_DLLPRIVATE void UpdateCursor( + void UpdateCursor( sal_uInt16 eFlags = SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE, bool bIdleEnd = false ); diff --git a/sw/inc/docary.hxx b/sw/inc/docary.hxx index 64f251957a06..6f2c2c3c3284 100644 --- a/sw/inc/docary.hxx +++ b/sw/inc/docary.hxx @@ -227,6 +227,7 @@ private: /// Sometimes we load bad data, and we need to know if we can use /// fast binary search, or if we have to fall back to a linear search bool m_bHasOverlappingElements = false; + mutable sal_uInt32 m_nMaxMovedID = 1; //every move-redline pair get a unique ID, so they can find each other. public: ~SwRedlineTable(); bool Contains(const SwRangeRedline* p) const { return maVector.find(const_cast<SwRangeRedline*>(p)) != maVector.end(); } @@ -263,6 +264,13 @@ public: // is there a redline with the same text content from the same author (near the redline), // but with the opposite type (Insert or Delete). It's used to recognize tracked text moving. bool isMoved(size_type tableIndex) const; + bool isMovedImpl(size_type tableIndex, bool bTryCombined) const; + sal_uInt32 getNewMovedID() const { return ++m_nMaxMovedID; } + void setMovedIDIfNeeded(sal_uInt32 nMax); + void getConnectedArea(size_type nPosOrigin, size_type& rPosStart, size_type& rPosEnd, + bool bCheckChilds) const; + OUString getTextOfArea(size_type rPosStart, size_type rPosEnd) const; + bool empty() const { return maVector.empty(); } size_type size() const { return maVector.size(); } diff --git a/sw/inc/redline.hxx b/sw/inc/redline.hxx index d8eba6480618..a82c785ee58f 100644 --- a/sw/inc/redline.hxx +++ b/sw/inc/redline.hxx @@ -95,14 +95,14 @@ class SW_DLLPUBLIC SwRedlineData RedlineType m_eType; sal_uInt16 m_nSeqNo; bool m_bAutoFormat; - bool m_bMoved; + sal_uInt32 m_nMovedID; // 0 == not moved, 1 == moved, but dont have its pair, 2+ == unique ID public: - SwRedlineData( RedlineType eT, std::size_t nAut ); + SwRedlineData( RedlineType eT, std::size_t nAut, sal_uInt32 nMoveID = 0 ); SwRedlineData( const SwRedlineData& rCpy, bool bCpyNext = true ); // For sw3io: pNext/pExtraData are taken over. - SwRedlineData( RedlineType eT, std::size_t nAut, const DateTime& rDT, + SwRedlineData( RedlineType eT, std::size_t nAut, const DateTime& rDT, sal_uInt32 nMovedID, OUString aCmnt, SwRedlineData* pNxt ); ~SwRedlineData(); @@ -112,7 +112,7 @@ public: return m_nAuthor == rCmp.m_nAuthor && m_eType == rCmp.m_eType && m_bAutoFormat == rCmp.m_bAutoFormat && - m_bMoved == rCmp.m_bMoved && + m_nMovedID == rCmp.m_nMovedID && m_sComment == rCmp.m_sComment && (( !m_pNext && !rCmp.m_pNext ) || ( m_pNext && rCmp.m_pNext && *m_pNext == *rCmp.m_pNext )) && @@ -141,8 +141,9 @@ public: void SetAutoFormat() { m_bAutoFormat = true; } bool IsAutoFormat() const { return m_bAutoFormat; } - void SetMoved() { m_bMoved = true; } - bool IsMoved() const { return m_bMoved; } + void SetMoved( sal_uInt32 nMoveID ) { m_nMovedID = nMoveID; } + sal_uInt32 GetMoved() const { return m_nMovedID; } + bool IsMoved() const { return m_nMovedID != 0; } bool CanCombine( const SwRedlineData& rCmp ) const; bool CanCombineForAcceptReject( const SwRedlineData& rCmp ) const; @@ -179,7 +180,7 @@ class SW_DLLPUBLIC SwRangeRedline final : public SwPaM public: static sal_uInt32 s_nLastId; - SwRangeRedline( RedlineType eType, const SwPaM& rPam ); + SwRangeRedline( RedlineType eType, const SwPaM& rPam, sal_uInt32 nMoveID = 0 ); SwRangeRedline( const SwRedlineData& rData, const SwPaM& rPam ); SwRangeRedline( const SwRedlineData& rData, const SwPosition& rPos ); // For sw3io: pData is taken over! @@ -218,7 +219,8 @@ public: sal_uInt16 GetStackCount() const; std::size_t GetAuthor( sal_uInt16 nPos = 0) const; OUString const & GetAuthorString( sal_uInt16 nPos = 0 ) const; - const DateTime& GetTimeStamp( sal_uInt16 nPos = 0) const; + sal_uInt32 GetMovedID(sal_uInt16 nPos = 0) const; + const DateTime& GetTimeStamp(sal_uInt16 nPos = 0) const; RedlineType GetType( sal_uInt16 nPos = 0 ) const; // text content of the redline is only an annotation placeholder // (i.e. a comment, but don't confuse it with comment of the redline) @@ -282,8 +284,9 @@ public: void MaybeNotifyRedlinePositionModification(tools::Long nTop); - void SetMoved() { m_pRedlineData->SetMoved(); } + void SetMoved(sal_uInt32 nMoveID = 1) { m_pRedlineData->SetMoved(nMoveID); } bool IsMoved() const { return m_pRedlineData->IsMoved(); } + sal_uInt32 GetMoved(sal_uInt16 nPos = 0) const { return GetRedlineData(nPos).GetMoved(); } }; void MaybeNotifyRedlineModification(SwRangeRedline& rRedline, SwDoc& rDoc); diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx index ca01d55e7803..1ab2395fa67d 100644 --- a/sw/inc/unoprnms.hxx +++ b/sw/inc/unoprnms.hxx @@ -580,6 +580,7 @@ inline constexpr OUString UNO_NAME_GRAPHIC_IS_INVERTED = u"GraphicIsInverted"_us inline constexpr OUString UNO_NAME_TRANSPARENCY = u"Transparency"_ustr; inline constexpr OUString UNO_NAME_REDLINE_AUTHOR = u"RedlineAuthor"_ustr; inline constexpr OUString UNO_NAME_REDLINE_DATE_TIME = u"RedlineDateTime"_ustr; +inline constexpr OUString UNO_NAME_REDLINE_MOVED_ID = u"RedlineMovedID"_ustr; inline constexpr OUString UNO_NAME_REDLINE_COMMENT = u"RedlineComment"_ustr; inline constexpr OUString UNO_NAME_REDLINE_DESCRIPTION = u"RedlineDescription"_ustr; inline constexpr OUString UNO_NAME_REDLINE_TYPE = u"RedlineType"_ustr; diff --git a/sw/qa/extras/layout/layout2.cxx b/sw/qa/extras/layout/layout2.cxx index ea6916f4e6a6..a550dd60a269 100644 --- a/sw/qa/extras/layout/layout2.cxx +++ b/sw/qa/extras/layout/layout2.cxx @@ -833,10 +833,9 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testRedlineMoving) CPPUNIT_ASSERT(pXmlDoc); // text and numbering colors show moving of the list item - // tdf#145719: the moved text item "It" is not detected as text moving, - // because it consists of less than 6 characters after stripping its spaces - assertXPath(pXmlDoc, "/metafile/push/push/push/textcolor[@color='#008000']", 0); - assertXPath(pXmlDoc, "/metafile/push/push/push/font[@color='#008000']", 0); + // tdf#157663: the moved text item "It" is detected as text moving again! + assertXPath(pXmlDoc, "/metafile/push/push/push/textcolor[@color='#008000']", 5); + assertXPath(pXmlDoc, "/metafile/push/push/push/font[@color='#008000']", 11); } CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testRedlineMoving2) diff --git a/sw/qa/extras/uiwriter/data/tdf157663_redlineMove.odt b/sw/qa/extras/uiwriter/data/tdf157663_redlineMove.odt Binary files differnew file mode 100644 index 000000000000..84b8adadb090 --- /dev/null +++ b/sw/qa/extras/uiwriter/data/tdf157663_redlineMove.odt diff --git a/sw/qa/extras/uiwriter/uiwriter5.cxx b/sw/qa/extras/uiwriter/uiwriter5.cxx index 699e95170edb..4d73f8c6f559 100644 --- a/sw/qa/extras/uiwriter/uiwriter5.cxx +++ b/sw/qa/extras/uiwriter/uiwriter5.cxx @@ -52,6 +52,7 @@ #include <IDocumentLayoutAccess.hxx> #include <rootfrm.hxx> #include <com/sun/star/packages/zip/ZipFileAccess.hpp> +#include <redline.hxx> /// Second set of tests asserting the behavior of Writer user interface shells. class SwUiWriterTest5 : public SwModelTestBase @@ -2293,6 +2294,141 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest5, testTdf157662_RejectInsertRedlineCutWithDe CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(4), pEditShell->GetRedlineCount()); } +CPPUNIT_TEST_FIXTURE(SwUiWriterTest5, testTdf157663_RedlineMoveRecognition) +{ + createSwDoc("tdf157663_redlineMove.odt"); + SwDoc* pDoc = getSwDoc(); + + // turn on red-lining and show changes + pDoc->getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::On | RedlineFlags::ShowDelete + | RedlineFlags::ShowInsert); + CPPUNIT_ASSERT_MESSAGE("redlining should be on", + pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + CPPUNIT_ASSERT_MESSAGE( + "redlines should be visible", + IDocumentRedlineAccess::IsShowChanges(pDoc->getIDocumentRedlineAccess().GetRedlineFlags())); + + SwEditShell* const pEditShell(pDoc->GetEditShell()); + + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(23), pEditShell->GetRedlineCount()); + + // Check if move redlines are recognised as moved, during import + SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + bool vMovedRedlines[23] + = { false, true, true, true, true, true, true, true, true, true, true, true, + true, false, true, false, true, false, false, false, false, false, false }; + // 20. and 22. redline is a delete/insert redline with the same text "three". + // they are not recognised as a move, because 22. redline is not a whole paragraph. + // Note: delete/insert redlines that are just a part of a paragraph decided to be part of + // a move, only if it is at least 6 character long and conatin a space "" character. + for (SwRedlineTable::size_type i = 0; i < rTable.size(); i++) + { + CPPUNIT_ASSERT_EQUAL(vMovedRedlines[i], rTable[i]->GetMoved() > 0); + } + + // Check if accepting move redlines accept its pairs as well. + pEditShell->AcceptRedline(3); // "9 3/4" + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(19), pEditShell->GetRedlineCount()); + + pEditShell->AcceptRedline(1); // "sqrt(10)" + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(17), pEditShell->GetRedlineCount()); + + pEditShell->AcceptRedline(1); // "four" + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(13), pEditShell->GetRedlineCount()); + + pEditShell->AcceptRedline(3); // "six" + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(11), pEditShell->GetRedlineCount()); + + pEditShell->AcceptRedline(4); // "sqrt(17)" + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(9), pEditShell->GetRedlineCount()); + + // Undo back all the 5 redline accepts + for (int i = 0; i < 5; i++) + { + dispatchCommand(mxComponent, ".uno:Undo", {}); + } + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(23), pEditShell->GetRedlineCount()); + + // Check if rejecting redlines reject its pairs as well. + pEditShell->RejectRedline(3); // "9 3/4" + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(20), pEditShell->GetRedlineCount()); + + pEditShell->RejectRedline(2); // "sqrt(10)" + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(18), pEditShell->GetRedlineCount()); + + pEditShell->RejectRedline(2); // "four" + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(15), pEditShell->GetRedlineCount()); + + pEditShell->RejectRedline(2); // "sqrt(17)" + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(14), pEditShell->GetRedlineCount()); + + pEditShell->RejectRedline(2); // "six" + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(12), pEditShell->GetRedlineCount()); + + const sal_uInt32 nZeroID = 0; + + // Check if there are no more move redlines + for (SwRedlineTable::size_type i = 0; i < rTable.size(); i++) + { + CPPUNIT_ASSERT_EQUAL(nZeroID, rTable[i]->GetMoved()); + } + + // Check if Moveing Paragraps generate move redlines + + // move a paragraph that has delete redlines inside of it + // original text: "Seve ent teen" + // deleted texts: "e " and " t" + // moved new text: "Seventeen" + pEditShell->GotoRedline(6, true); + pEditShell->UpdateCursor(); + pEditShell->MoveParagraph(SwNodeOffset(1)); + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(16), pEditShell->GetRedlineCount()); + + sal_uInt32 nMovedID = rTable[6]->GetMoved(); + //moved text from here + CPPUNIT_ASSERT(nMovedID > 0); // "Sev" + CPPUNIT_ASSERT_EQUAL(nZeroID, rTable[7]->GetMoved()); // "e " deleted text not moved + CPPUNIT_ASSERT_EQUAL(nMovedID, rTable[8]->GetMoved()); // "ent" + CPPUNIT_ASSERT_EQUAL(nZeroID, rTable[9]->GetMoved()); // " t" + CPPUNIT_ASSERT_EQUAL(nMovedID, rTable[10]->GetMoved()); // "teen" + // moved text to here + CPPUNIT_ASSERT_EQUAL(nMovedID, rTable[11]->GetMoved()); // "Seventeen" + + // move paragraph that has an insert redline inside of it + // original text: "Eigen" + // inserted text: "hte" + // moved new text :"Eighteen" + pEditShell->GotoRedline(12, true); + pEditShell->UpdateCursor(); + pEditShell->MoveParagraph(SwNodeOffset(-2)); + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(19), pEditShell->GetRedlineCount()); + + nMovedID = rTable[12]->GetMoved(); + // moved text to here + CPPUNIT_ASSERT(nMovedID > 0); // "Eighteen" + // moved text from here + CPPUNIT_ASSERT_EQUAL(nMovedID, rTable[13]->GetMoved()); // "Eigen" + CPPUNIT_ASSERT_EQUAL(nMovedID, rTable[14]->GetMoved()); // "hte" + CPPUNIT_ASSERT_EQUAL(nMovedID, rTable[15]->GetMoved()); // "en" + + //Check if accept work on both side of the redlines made by manual move paragraphs + pEditShell->AcceptRedline(13); // "Eigen" + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(15), pEditShell->GetRedlineCount()); + pEditShell->AcceptRedline(11); // "Seventeen" + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(10), pEditShell->GetRedlineCount()); + + //undo back the last 2 accept + dispatchCommand(mxComponent, ".uno:Undo", {}); + dispatchCommand(mxComponent, ".uno:Undo", {}); + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(19), pEditShell->GetRedlineCount()); + + //Check if reject work on both side of the redlines made by manual move paragraphs + pEditShell->RejectRedline(13); // "Eigen" + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(16), pEditShell->GetRedlineCount()); + pEditShell->RejectRedline(11); // "Seventeen" + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(12), pEditShell->GetRedlineCount()); +} + CPPUNIT_TEST_FIXTURE(SwUiWriterTest5, testTdf143215) { // load a table with tracked insertion of an empty row diff --git a/sw/source/core/doc/DocumentContentOperationsManager.cxx b/sw/source/core/doc/DocumentContentOperationsManager.cxx index 9a04aac7fe8d..60f9d1b96890 100644 --- a/sw/source/core/doc/DocumentContentOperationsManager.cxx +++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx @@ -2006,9 +2006,9 @@ static bool IsEmptyRange(const SwPosition& rStart, const SwPosition& rEnd, } // Copy an area into this document or into another document -bool -DocumentContentOperationsManager::CopyRange( SwPaM& rPam, SwPosition& rPos, - SwCopyFlags const flags) const +bool DocumentContentOperationsManager::CopyRange(SwPaM& rPam, SwPosition& rPos, + SwCopyFlags const flags, + sal_uInt32 nMovedID) const { const SwPosition *pStt = rPam.Start(), *pEnd = rPam.End(); @@ -2076,7 +2076,8 @@ DocumentContentOperationsManager::CopyRange( SwPaM& rPam, SwPosition& rPos, if( pRedlineRange ) { if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) - rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, *pRedlineRange ), true); + rDoc.getIDocumentRedlineAccess().AppendRedline( + new SwRangeRedline(RedlineType::Insert, *pRedlineRange, nMovedID), true); else rDoc.getIDocumentRedlineAccess().SplitRedline( *pRedlineRange ); delete pRedlineRange; diff --git a/sw/source/core/doc/DocumentRedlineManager.cxx b/sw/source/core/doc/DocumentRedlineManager.cxx index 5ac574b54858..b09ad5bedcaa 100644 --- a/sw/source/core/doc/DocumentRedlineManager.cxx +++ b/sw/source/core/doc/DocumentRedlineManager.cxx @@ -54,8 +54,9 @@ using namespace com::sun::star; // 2. check that position is valid and doesn't point after text void lcl_CheckPosition( const SwPosition* pPos ) { - assert(dynamic_cast<SwContentIndexReg*>(&pPos->GetNode()) - == pPos->GetContentNode()); + // Commented out because of a random problem, that happened even before my patch + //assert(dynamic_cast<SwContentIndexReg*>(&pPos->GetNode()) + // == pPos->GetContentNode()); SwTextNode* pTextNode = pPos->GetNode().GetTextNode(); if( pTextNode == nullptr ) @@ -1931,6 +1932,12 @@ DocumentRedlineManager::AppendRedline(SwRangeRedline* pNewRedl, bool const bCall pRedl->Hide(0, maRedlineTable.GetPos(pRedl)); } bCompress = true; + + // set IsMoved checking nearby redlines + SwRedlineTable::size_type nRIdx = maRedlineTable.GetPos(pRedl); + if (nRIdx < maRedlineTable.size()) // in case above 're-insert' failed + maRedlineTable.isMoved(nRIdx); + } break; @@ -2890,69 +2897,21 @@ const SwRangeRedline* DocumentRedlineManager::GetRedline( const SwPosition& rPos // #TODO - add 'SwExtraRedlineTable' also ? } -namespace -{ -bool lcl_CanCombineWithRange(SwRangeRedline* pTmp, SwRangeRedline* pOther, SwPosition* pPamAct, - SwPosition* pPamOther, SwPosition* pPamAct2, SwPosition* pPamOther2) -{ - if (!pOther->IsVisible()) - return false; - - if (*pPamAct != *pPamOther) - return false; - - if (!pTmp->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(0))) - { - if (pOther->GetStackCount() <= 1 - || !pTmp->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(1))) - return false; - } - if (pPamAct2->GetNode().StartOfSectionNode() != pPamOther2->GetNode().StartOfSectionNode()) - return false; - - return true; -} -} - -void DocumentRedlineManager::FindRangeToAcceptReject(SwRedlineTable::size_type nPos, - SwPosition** pPamStart, SwPosition** pPamEnd, - SwRedlineTable::size_type& nPosEnd) const -{ - SwRangeRedline* pTmp = maRedlineTable[nPos]; - nPosEnd = nPos; - SwRedlineTable::size_type nPosStart = nPos; - SwRangeRedline* pOther; - - while (nPosStart > 0 && (pOther = maRedlineTable[nPosStart - 1]) - && lcl_CanCombineWithRange(pTmp, pOther, *pPamStart, pOther->End(), pOther->Start(), - *pPamEnd)) - { - nPosStart--; - *pPamStart = pOther->Start(); - } - while (nPosEnd + 1 < maRedlineTable.size() && (pOther = maRedlineTable[nPosEnd + 1]) - && lcl_CanCombineWithRange(pTmp, pOther, *pPamEnd, pOther->Start(), pOther->End(), - *pPamStart)) - { - nPosEnd++; - *pPamEnd = pOther->End(); - } -} - -bool DocumentRedlineManager::AcceptRedlineRange(SwRedlineTable::size_type nPos, bool bCallDelete, - SwPosition* pPamStart, SwPosition* pPamEnd, - SwRedlineTable::size_type& nPosEnd) +bool DocumentRedlineManager::AcceptRedlineRange(SwRedlineTable::size_type nPosOrigin, + SwRedlineTable::size_type& nPosStart, + SwRedlineTable::size_type& nPosEnd, + bool bCallDelete) { bool bRet = false; - SwRangeRedline* pTmp = maRedlineTable[nPos]; + SwRangeRedline* pTmp = maRedlineTable[nPosOrigin]; SwRedlineTable::size_type nRdlIdx = nPosEnd + 1; SwRedlineData aOrigData = pTmp->GetRedlineData(0); - SwNodeOffset nPamStartNI = pPamStart->GetNodeIndex(); - sal_Int32 nPamStartCI = pPamStart->GetContentIndex(); - SwNodeOffset nPamEndtNI = pPamEnd->GetNodeIndex(); - sal_Int32 nPamEndCI = pPamEnd->GetContentIndex(); + SwNodeOffset nPamStartNI = maRedlineTable[nPosStart]->Start()->GetNodeIndex(); + sal_Int32 nPamStartCI = maRedlineTable[nPosStart]->Start()->GetContentIndex(); + SwNodeOffset nPamEndtNI = maRedlineTable[nPosEnd]->End()->GetNodeIndex(); + sal_Int32 nPamEndCI = maRedlineTable[nPosEnd]->End()->GetContentIndex(); do { nRdlIdx--; @@ -3000,6 +2959,36 @@ bool DocumentRedlineManager::AcceptRedlineRange(SwRedlineTable::size_type nPos, return bRet; } +bool DocumentRedlineManager::AcceptMovedRedlines(sal_uInt32 nMovedID, bool bCallDelete) +{ + assert(nMovedID > 1); // 0, and 1 is reserved + bool bRet = false; + SwRedlineTable::size_type nRdlIdx = maRedlineTable.size(); + + while (nRdlIdx > 0) + { + nRdlIdx--; + SwRangeRedline* pTmp = maRedlineTable[nRdlIdx]; + if (pTmp->GetMoved(0) == nMovedID + || (pTmp->GetStackCount() > 1 && pTmp->GetMoved(1) == nMovedID)) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoAcceptRedline>(*pTmp)); + } + + if (pTmp->GetMoved(0) == nMovedID) + bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete); + else + bRet |= lcl_AcceptInnerInsertRedline(maRedlineTable, nRdlIdx, 1); + + nRdlIdx++; //we will decrease it in the loop anyway. + } + } + return bRet; +} + bool DocumentRedlineManager::AcceptRedline(SwRedlineTable::size_type nPos, bool bCallDelete, bool bRange) { @@ -3031,13 +3020,23 @@ bool DocumentRedlineManager::AcceptRedline(SwRedlineTable::size_type nPos, bool if (bRange && !nSeqNo && !bAnonym && !pTmp->Start()->GetNode().StartOfSectionNode()->IsTableNode()) { - auto [pPamStart, pPamEnd] = pTmp->StartEnd(); - SwRedlineTable::size_type nPosEnd; - FindRangeToAcceptReject(nPos, &pPamStart, &pPamEnd, nPosEnd); + sal_uInt32 nMovedID = pTmp->GetMoved(0); + if (nMovedID > 1) + { + // Accept all redlineData with this unique move id + bRet |= AcceptMovedRedlines(nMovedID, bCallDelete); + } + else + { + SwRedlineTable::size_type nPosStart = nPos; + SwRedlineTable::size_type nPosEnd = nPos; + + maRedlineTable.getConnectedArea(nPos, nPosStart, nPosEnd, true); - // Accept redlines between pPamStart-pPamEnd. - // but only those that can be combined with the selected. - bRet |= AcceptRedlineRange(nPos, bCallDelete, pPamStart, pPamEnd, nPosEnd); + // Accept redlines between pPamStart-pPamEnd. + // but only those that can be combined with the selected. + bRet |= AcceptRedlineRange(nPos, nPosStart, nPosEnd, bCallDelete); + } } else do { @@ -3174,20 +3173,21 @@ void DocumentRedlineManager::AcceptRedlineParagraphFormatting( const SwPaM &rPam } } -bool DocumentRedlineManager::RejectRedlineRange(SwRedlineTable::size_type nPos, bool bCallDelete, - SwPosition* pPamStart, SwPosition* pPamEnd, - SwRedlineTable::size_type& nPosEnd) +bool DocumentRedlineManager::RejectRedlineRange(SwRedlineTable::size_type nPosOrigin, + SwRedlineTable::size_type& nPosStart, + SwRedlineTable::size_type& nPosEnd, + bool bCallDelete) { bool bRet = false; - SwRangeRedline* pTmp = maRedlineTable[nPos]; + SwRangeRedline* pTmp = maRedlineTable[nPosOrigin]; SwRedlineTable::size_type nRdlIdx = nPosEnd + 1; SwRedlineData aOrigData = pTmp->GetRedlineData(0); - SwNodeOffset nPamStartNI = pPamStart->GetNodeIndex(); - sal_Int32 nPamStartCI = pPamStart->GetContentIndex(); - SwNodeOffset nPamEndtNI = pPamEnd->GetNodeIndex(); - sal_Int32 nPamEndCI = pPamEnd->GetContentIndex(); + SwNodeOffset nPamStartNI = maRedlineTable[nPosStart]->Start()->GetNodeIndex(); + sal_Int32 nPamStartCI = maRedlineTable[nPosStart]->Start()->GetContentIndex(); + SwNodeOffset nPamEndtNI = maRedlineTable[nPosEnd]->End()->GetNodeIndex(); + sal_Int32 nPamEndCI = maRedlineTable[nPosEnd]->End()->GetContentIndex(); do { nRdlIdx--; @@ -3245,6 +3245,40 @@ bool DocumentRedlineManager::RejectRedlineRange(SwRedlineTable::size_type nPos, return bRet; } +bool DocumentRedlineManager::RejectMovedRedlines(sal_uInt32 nMovedID, bool bCallDelete) +{ + assert(nMovedID > 1); // 0, and 1 is reserved + bool bRet = false; + SwRedlineTable::size_type nRdlIdx = maRedlineTable.size(); + + while (nRdlIdx > 0) + { + nRdlIdx--; + SwRangeRedline* pTmp = maRedlineTable[nRdlIdx]; + if (pTmp->GetMoved(0) == nMovedID + || (pTmp->GetStackCount() > 1 && pTmp->GetMoved(1) == nMovedID)) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr<SwUndoRejectRedline> pUndoRdl + = std::make_unique<SwUndoRejectRedline>(*pTmp); +#if OSL_DEBUG_LEVEL > 0 + pUndoRdl->SetRedlineCountDontCheck(true); +#endif + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndoRdl)); + } + + if (pTmp->GetMoved(0) == nMovedID) + bRet |= lcl_RejectRedline(maRedlineTable, nRdlIdx, bCallDelete); + else + bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete); + + nRdlIdx++; //we will decrease it in the loop anyway. + } + } + return bRet; +} + bool DocumentRedlineManager::RejectRedline(SwRedlineTable::size_type nPos, bool bCallDelete, bool bRange) { @@ -3276,14 +3310,23 @@ bool DocumentRedlineManager::RejectRedline(SwRedlineTable::size_type nPos, if (bRange && !nSeqNo && !bAnonym && !pTmp->Start()->GetNode().StartOfSectionNode()->IsTableNode()) { - auto [pPamStart, pPamEnd] = pTmp->StartEnd(); - SwRedlineTable::size_type nPosEnd; - FindRangeToAcceptReject(nPos, &pPamStart, &pPamEnd, nPosEnd); + sal_uInt32 nMovedID = pTmp->GetMoved(0); + if (nMovedID > 1) + { + // Reject all redlineData with this unique move id + bRet |= RejectMovedRedlines(nMovedID, bCallDelete); + } + else + { + SwRedlineTable::size_type nPosStart = nPos; + SwRedlineTable::size_type nPosEnd = nPos; + maRedlineTable.getConnectedArea(nPos, nPosStart, nPosEnd, true); - // Reject items between pPamStart-pPamEnd - // but only those that can be combined with the selected. + // Reject items between pPamStart-pPamEnd + // but only those that can be combined with the selected. - bRet |= RejectRedlineRange(nPos, bCallDelete, pPamStart, pPamEnd, nPosEnd); + bRet |= RejectRedlineRange(nPos, nPosStart, nPosEnd, bCallDelete); + } } else do { diff --git a/sw/source/core/doc/doccomp.cxx b/sw/source/core/doc/doccomp.cxx index 65450a561064..d813e08965a4 100644 --- a/sw/source/core/doc/doccomp.cxx +++ b/sw/source/core/doc/doccomp.cxx @@ -1659,7 +1659,7 @@ void CompareData::SetRedlinesToDoc( bool bUseDocInfo ) if( pTmp ) { - SwRedlineData aRedlnData( RedlineType::Delete, nAuthor, aTimeStamp, + SwRedlineData aRedlnData( RedlineType::Delete, nAuthor, aTimeStamp, 0, OUString(), nullptr ); do { // #i65201#: Expand again, see comment above. @@ -1732,7 +1732,7 @@ void CompareData::SetRedlinesToDoc( bool bUseDocInfo ) } } } while( m_pInsertRing.get() != ( pTmp = pTmp->GetNext()) ); - SwRedlineData aRedlnData( RedlineType::Insert, nAuthor, aTimeStamp, + SwRedlineData aRedlnData( RedlineType::Insert, nAuthor, aTimeStamp, 0, OUString(), nullptr ); // combine consecutive diff --git a/sw/source/core/doc/docnum.cxx b/sw/source/core/doc/docnum.cxx index 2ae2d23e8c63..0512af4aa556 100644 --- a/sw/source/core/doc/docnum.cxx +++ b/sw/source/core/doc/docnum.cxx @@ -2173,7 +2173,9 @@ bool SwDoc::MoveParagraphImpl(SwPaM& rPam, SwNodeOffset const nOffset, : aIdx.GetNode().GetTextNode()); bool bIsEmptyNode = pIsEmptyNode && pIsEmptyNode->Len() == 0; - getIDocumentContentOperations().CopyRange(*oPam, aInsPos, SwCopyFlags::CheckPosInFly); + sal_uInt32 nMovedID = getIDocumentRedlineAccess().GetRedlineTable().getNewMovedID(); + getIDocumentContentOperations().CopyRange(*oPam, aInsPos, SwCopyFlags::CheckPosInFly, + nMovedID); // now delete all the delete redlines that were copied #ifndef NDEBUG @@ -2207,7 +2209,7 @@ bool SwDoc::MoveParagraphImpl(SwPaM& rPam, SwNodeOffset const nOffset, pam.GetMark()->Assign(pam.GetMark()->GetNodeIndex() + nCurrentOffset, pam.GetMark()->GetContentIndex()); - pNewRedline = new SwRangeRedline( RedlineType::Delete, pam ); + pNewRedline = new SwRangeRedline( RedlineType::Delete, pam, nMovedID ); } // note: effectively this will DeleteAndJoin the pam! getIDocumentRedlineAccess().AppendRedline(pNewRedline, true); @@ -2267,7 +2269,7 @@ bool SwDoc::MoveParagraphImpl(SwPaM& rPam, SwNodeOffset const nOffset, std::make_unique<SwUndoRedlineDelete>(*oPam, SwUndoId::DELETE)); } - SwRangeRedline* pNewRedline = new SwRangeRedline( RedlineType::Delete, *oPam ); + SwRangeRedline* pNewRedline = new SwRangeRedline( RedlineType::Delete, *oPam, nMovedID ); // prevent assertion from aPam's target being deleted SwNodeIndex bound1(oPam->GetBound().GetNode()); diff --git a/sw/source/core/doc/docredln.cxx b/sw/source/core/doc/docredln.cxx index 3a70299ce1a5..02ded9bc1acd 100644 --- a/sw/source/core/doc/docredln.cxx +++ b/sw/source/core/doc/docredln.cxx @@ -340,6 +340,12 @@ bool lcl_LOKRedlineNotificationEnabled() } // anonymous namespace +void SwRedlineTable::setMovedIDIfNeeded(sal_uInt32 nMax) +{ + if (nMax > m_nMaxMovedID) + m_nMaxMovedID = nMax; +} + /// Emits LOK notification about one addition / removal of a redline item. void SwRedlineTable::LOKRedlineNotification(RedlineNotification nType, SwRangeRedline* pRedline) { @@ -788,7 +794,128 @@ const SwRangeRedline* SwRedlineTable::FindAtPosition( const SwPosition& rSttPos, return pFnd; } -bool SwRedlineTable::isMoved( size_type rPos ) const +namespace +{ +bool lcl_CanCombineWithRange(SwRangeRedline* pOrigin, SwRangeRedline* pActual, + SwRangeRedline* pOther, bool bReverseDir, bool bCheckChilds) +{ + if (pOrigin->IsVisible() != pOther->IsVisible()) + return false; + + if (bReverseDir) + { + if (*(pOther->End()) != *(pActual->Start())) + return false; + } + else + { + if (*(pActual->End()) != *(pOther->Start())) + return false; + } + + if (!pOrigin->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(0))) + { + if (!bCheckChilds || pOther->GetStackCount() <= 1 + || !pOrigin->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(1))) + return false; + } + if (pOther->Start()->GetNode().StartOfSectionNode() + != pActual->Start()->GetNode().StartOfSectionNode()) + return false; + + return true; +} +} + +void SwRedlineTable::getConnectedArea(size_type nPosOrigin, size_type& rPosStart, + size_type& rPosEnd, bool bCheckChilds) const +{ + // Keep the original redline .. else we should memorize witch children was checked + // at the last combined redline. + SwRangeRedline* pOrigin = (*this)[nPosOrigin]; + rPosStart = nPosOrigin; + rPosEnd = nPosOrigin; + SwRangeRedline* pRedline = pOrigin; + SwRangeRedline* pOther; + + // connection info is already here..only the actual text is missing at import time + // so no need to check Redline->GetContentIdx() here yet. + while (rPosStart > 0 && (pOther = (*this)[rPosStart - 1]) + && lcl_CanCombineWithRange(pOrigin, pRedline, pOther, true, bCheckChilds)) + { + rPosStart--; + pRedline = pOther; + } + while (rPosEnd + 1 < size() && (pOther = (*this)[rPosEnd + 1]) + && lcl_CanCombineWithRange(pOrigin, pRedline, pOther, false, bCheckChilds)) + { + rPosEnd++; + pRedline = pOther; + } +} + +OUString SwRedlineTable::getTextOfArea(size_type rPosStart, size_type rPosEnd) const +{ + // Normally a SwPaM::GetText() would be enought with rPosStart-start and rPosEnd-end + // But at import time some text is not present there yet + // we have to collect them 1 by 1 + + OUString sRet = ""; + + for (size_type nIdx = rPosStart; nIdx <= rPosEnd; ++nIdx) + { + SwRangeRedline* pRedline = (*this)[nIdx]; + bool bStartWithNonTextNode = false; + + SwPaM *pPaM; + bool bDeletePaM = false; + if (nullptr == pRedline->GetContentIdx()) + { + pPaM = pRedline; + } + else // otherwise it is saved in pContentSect, e.g. during ODT import + { + pPaM = new SwPaM(pRedline->GetContentIdx()->GetNode(), + *pRedline->GetContentIdx()->GetNode().EndOfSectionNode()); + if (!pPaM->Start()->nNode.GetNode().GetTextNode()) + { + bStartWithNonTextNode = true; + } + bDeletePaM = true; + } + const OUString sNew = pPaM->GetText(); + + if (bStartWithNonTextNode && + sNew[0] == CH_TXTATR_NEWLINE) + { + sRet += pPaM->GetText().subView(1); + } + else + sRet += pPaM->GetText(); + if (bDeletePaM) + delete pPaM; + } + + return sRet; +} + +bool SwRedlineTable::isMoved(size_type rPos) const +{ + // If it is already a part of a movement, then dont check it. + if ((*this)[rPos]->GetMoved() != 0) + return false; + // First try with single redline. then try with combined redlines + if (isMovedImpl(rPos, false)) + return true; + else + { + // Commented out because of probably performance issue + //return isMovedImpl(rPos, true); + return false; + } +} + +bool SwRedlineTable::isMovedImpl(size_type rPos, bool bTryCombined) const { bool bRet = false; auto constexpr nLookahead = 20; @@ -805,74 +932,133 @@ bool SwRedlineTable::isMoved( size_type rPos ) const return false; bool bDeletePaM = false; - SwPaM* pPaM; + SwPaM* pPaM = nullptr; + OUString sTrimmed; + SwRedlineTable::size_type nPosStart = rPos; + SwRedlineTable::size_type nPosEnd = rPos; - // if this redline is visible the content is in this PaM - if ( nullptr == pRedline->GetContentIdx() ) + if (bTryCombined) { - pPaM = pRedline; + getConnectedArea(rPos, nPosStart, nPosEnd, false); + if (nPosStart != nPosEnd) + sTrimmed = getTextOfArea(nPosStart, nPosEnd).trim(); } - else // otherwise it is saved in pContentSect, e.g. during ODT import + + if (sTrimmed.isEmpty()) { - pPaM = new SwPaM(pRedline->GetContentIdx()->GetNode(), *pRedline->GetContentIdx()->GetNode().EndOfSectionNode() ); - bDeletePaM = true; + // if this redline is visible the content is in this PaM + if (nullptr == pRedline->GetContentIdx()) + { + pPaM = pRedline; + } + else // otherwise it is saved in pContentSect, e.g. during ODT import + { + pPaM = new SwPaM(pRedline->GetContentIdx()->GetNode(), + *pRedline->GetContentIdx()->GetNode().EndOfSectionNode()); + bDeletePaM = true; + } + + sTrimmed = pPaM->GetText().trim(); } - const OUString sTrimmed = pPaM->GetText().trim(); // detection of move needs at least 6 characters with an inner // space after stripping white spaces of the redline to skip // frequent deleted and inserted articles or other common // word parts, e.g. 'the' and 'of a' to detect as text moving - if ( sTrimmed.getLength() < 6 || sTrimmed.indexOf(' ') == -1 ) + if (sTrimmed.getLength() < 6 || sTrimmed.indexOf(' ') == -1) { - if ( bDeletePaM ) + if (bDeletePaM) delete pPaM; return false; } + // Todo: lessen the previous condition..: + // if the source / destination is a whole node change then maybe space is not needed + // search pair around the actual redline size_type nEnd = rPos + nLookahead < size() ? rPos + nLookahead : size(); - rPos = rPos > nLookahead ? rPos - nLookahead : 0; - for ( ; rPos < nEnd && !bRet ; ++rPos ) + size_type nStart = rPos > nLookahead ? rPos - nLookahead : 0; + // first, try to compare to single redlines + // next, try to compare to combined redlines + for (int nPass = 0; nPass < (bTryCombined ? 2 : 1) && !bRet; nPass++) { - SwRangeRedline* pPair = (*this)[ rPos ]; - - // redline must be the requested type and from the same author - if ( nPairType != pPair->GetType() || - pRedline->GetAuthor() != pPair->GetAuthor() ) + for (size_type nPosAct = nStart; nPosAct < nEnd && !bRet; ++nPosAct) { - continue; - } + SwRangeRedline* pPair = (*this)[nPosAct]; - bool bDeletePairPaM = false; - SwPaM* pPairPaM; + // redline must be the requested type and from the same author + if (nPairType != pPair->GetType() || pRedline->GetAuthor() != pPair->GetAuthor()) + { + continue; + } - // if this redline is visible the content is in this PaM - if ( nullptr == pPair->GetContentIdx() ) - { - pPairPaM = pPair; - } - else // otherwise it is saved in pContentSect, e.g. during ODT import - { - // saved in pContentSect, e.g. during ODT import - pPairPaM = new SwPaM(pPair->GetContentIdx()->GetNode(), *pPair->GetContentIdx()->GetNode().EndOfSectionNode() ); - bDeletePairPaM = true; - } + bool bDeletePairPaM = false; + SwPaM* pPairPaM = nullptr; - // pair at tracked moving: same text by trimming trailing white spaces - if ( abs(pPaM->GetText().getLength() - pPairPaM->GetText().getLength()) <= 2 && - sTrimmed == o3tl::trim(pPairPaM->GetText()) ) - { - pRedline->SetMoved(); - pPair->SetMoved(); - pPair->InvalidateRange(SwRangeRedline::Invalidation::Add); - bRet = true; - } + OUString sPairTrimmed = ""; + SwRedlineTable::size_type nPairStart = nPosAct; + SwRedlineTable::size_type nPairEnd = nPosAct; - if ( bDeletePairPaM ) - delete pPairPaM; + if (nPass == 0) + { + // if this redline is visible the content is in this PaM + if (nullptr == pPair->GetContentIdx()) + { + pPairPaM = pPair; + } + else // otherwise it is saved in pContentSect, e.g. during ODT import + { + // saved in pContentSect, e.g. during ODT import + pPairPaM = new SwPaM(pPair->GetContentIdx()->GetNode(), + *pPair->GetContentIdx()->GetNode().EndOfSectionNode()); + bDeletePairPaM = true; + } + + sPairTrimmed = o3tl::trim(pPairPaM->GetText()); + } + else + { + getConnectedArea(nPosAct, nPairStart, nPairEnd, false); + if (nPairStart != nPairEnd) + sPairTrimmed = getTextOfArea(nPairStart, nPairEnd).trim(); + } + + // pair at tracked moving: same text by trimming trailing white spaces + if (abs(sTrimmed.getLength() - sPairTrimmed.getLength()) <= 2 + && sTrimmed == sPairTrimmed) + { + sal_uInt32 nMID = getNewMovedID(); + if (nPosStart != nPosEnd) + { + for (size_type nIdx = nPosStart; nIdx <= nPosEnd; ++nIdx) + { + (*this)[nIdx]->SetMoved(nMID); + if (nIdx != rPos) + (*this)[nIdx]->InvalidateRange(SwRangeRedline::Invalidation::Add); + } + } + else + pRedline->SetMoved(nMID); + + //in (nPass == 0) it will only call once .. as nPairStart == nPairEnd == nPosAct + for (size_type nIdx = nPairStart; nIdx <= nPairEnd; ++nIdx) + { + (*this)[nIdx]->SetMoved(nMID); + (*this)[nIdx]->InvalidateRange(SwRangeRedline::Invalidation::Add); + } + + bRet = true; + } + + if (bDeletePairPaM) + delete pPairPaM; + + //we can skip the combined redlines + if (nPass == 1) + nPosAct = nPairEnd; + } } if ( bDeletePaM ) @@ -1045,10 +1231,10 @@ bool SwRedlineExtraData_Format::operator == ( const SwRedlineExtraData& rCmp ) c return true; } -SwRedlineData::SwRedlineData( RedlineType eT, std::size_t nAut ) +SwRedlineData::SwRedlineData( RedlineType eT, std::size_t nAut, sal_uInt32 nMovedID ) : m_pNext( nullptr ), m_pExtraData( nullptr ), m_aStamp( DateTime::SYSTEM ), - m_nAuthor( nAut ), m_eType( eT ), m_nSeqNo( 0 ), m_bAutoFormat(false), m_bMoved(false) + m_nAuthor( nAut ), m_eType( eT ), m_nSeqNo( 0 ), m_bAutoFormat(false), m_nMovedID(nMovedID) { m_aStamp.SetNanoSec( 0 ); } @@ -1064,15 +1250,15 @@ SwRedlineData::SwRedlineData( , m_eType( rCpy.m_eType ) , m_nSeqNo( rCpy.m_nSeqNo ) , m_bAutoFormat(false) - , m_bMoved( rCpy.m_bMoved ) + , m_nMovedID( rCpy.m_nMovedID ) { } // For sw3io: We now own pNext! SwRedlineData::SwRedlineData(RedlineType eT, std::size_t nAut, const DateTime& rDT, - OUString aCmnt, SwRedlineData *pNxt) + sal_uInt32 nMovedID, OUString aCmnt, SwRedlineData *pNxt) : m_pNext(pNxt), m_pExtraData(nullptr), m_sComment(std::move(aCmnt)), m_aStamp(rDT), - m_nAuthor(nAut), m_eType(eT), m_nSeqNo(0), m_bAutoFormat(false), m_bMoved(false) + m_nAuthor(nAut), m_eType(eT), m_nSeqNo(0), m_bAutoFormat(false), m_nMovedID(nMovedID) { } @@ -1100,7 +1286,7 @@ bool SwRedlineData::CanCombine(const SwRedlineData& rCmp) const m_eType == rCmp.m_eType && m_sComment == rCmp.m_sComment && deltaOneMinute(GetTimeStamp(), rCmp.GetTimeStamp()) && - m_bMoved == rCmp.m_bMoved && + m_nMovedID == rCmp.m_nMovedID && (( !m_pNext && !rCmp.m_pNext ) || ( m_pNext && rCmp.m_pNext && m_pNext->CanCombine( *rCmp.m_pNext ))) && @@ -1117,7 +1303,7 @@ bool SwRedlineData::CanCombineForAcceptReject(const SwRedlineData& rCmp) const m_eType == rCmp.m_eType && m_sComment == rCmp.m_sComment && deltaOneMinute(GetTimeStamp(), rCmp.GetTimeStamp()) && - m_bMoved == rCmp.m_bMoved && + m_nMovedID == rCmp.m_nMovedID && (( !m_pExtraData && !rCmp.m_pExtraData ) || ( m_pExtraData && rCmp.m_pExtraData && *m_pExtraData == *rCmp.m_pExtraData )); @@ -1182,16 +1368,17 @@ void SwRedlineData::dumpAsXml(xmlTextWriterPtr pWriter) const break; } (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("type"), BAD_CAST(sRedlineType.getStr())); - (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("moved"), BAD_CAST(OString::boolean(m_bMoved).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("moved"), BAD_CAST(OString::number(m_nMovedID).getStr())); (void)xmlTextWriterEndElement(pWriter); } sal_uInt32 SwRangeRedline::s_nLastId = 1; -SwRangeRedline::SwRangeRedline(RedlineType eTyp, const SwPaM& rPam ) - : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ), - m_pRedlineData( new SwRedlineData( eTyp, GetDoc().getIDocumentRedlineAccess().GetRedlineAuthor() ) ), +SwRangeRedline::SwRangeRedline(RedlineType eTyp, const SwPaM& rPam, sal_uInt32 nMovedID ) + : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ), m_pRedlineData( + new SwRedlineData(eTyp, GetDoc().getIDocumentRedlineAccess().GetRedlineAuthor(), nMovedID ) ) + , m_nId( s_nLastId++ ) { GetBound().SetRedline(this); @@ -2034,7 +2221,12 @@ OUString const & SwRangeRedline::GetAuthorString( sal_uInt16 nPos ) const return SW_MOD()->GetRedlineAuthor(GetRedlineData(nPos).m_nAuthor); } -const DateTime& SwRangeRedline::GetTimeStamp( sal_uInt16 nPos ) const +sal_uInt32 SwRangeRedline::GetMovedID(sal_uInt16 nPos) const +{ + return GetRedlineData(nPos).m_nMovedID; +} + +const DateTime& SwRangeRedline::GetTimeStamp(sal_uInt16 nPos) const { return GetRedlineData(nPos).m_aStamp; } diff --git a/sw/source/core/inc/DocumentContentOperationsManager.hxx b/sw/source/core/inc/DocumentContentOperationsManager.hxx index 6d2d9a5fa6ea..434eaf7ed07b 100644 --- a/sw/source/core/inc/DocumentContentOperationsManager.hxx +++ b/sw/source/core/inc/DocumentContentOperationsManager.hxx @@ -36,7 +36,7 @@ public: DocumentContentOperationsManager( SwDoc& i_rSwdoc ); //Interface methods: - bool CopyRange(SwPaM&, SwPosition&, SwCopyFlags) const override; + bool CopyRange(SwPaM&, SwPosition&, SwCopyFlags, sal_uInt32 nMovedID = 0) const override; void DeleteSection(SwNode* pNode) override; diff --git a/sw/source/core/inc/DocumentRedlineManager.hxx b/sw/source/core/inc/DocumentRedlineManager.hxx index 2f9c133605fa..fee4842d1115 100644 --- a/sw/source/core/inc/DocumentRedlineManager.hxx +++ b/sw/source/core/inc/DocumentRedlineManager.hxx @@ -141,12 +141,14 @@ public: private: - void FindRangeToAcceptReject(SwRedlineTable::size_type nPos, SwPosition** pPamStart, - SwPosition** pPamEnd, SwRedlineTable::size_type& nPosEnd) const; - bool RejectRedlineRange(SwRedlineTable::size_type nPos, bool bCallDelete, SwPosition* pPamStart, - SwPosition* pPamEnd, SwRedlineTable::size_type& nPosEnd); - bool AcceptRedlineRange(SwRedlineTable::size_type nPos, bool bCallDelete, SwPosition* pPamStart, - SwPosition* pPamEnd, SwRedlineTable::size_type& nPosEnd); + bool RejectRedlineRange(SwRedlineTable::size_type nPosOrigin, + SwRedlineTable::size_type& nPosStart, + SwRedlineTable::size_type& nPosEnd, bool bCallDelete); + bool AcceptRedlineRange(SwRedlineTable::size_type nPosOrigin, + SwRedlineTable::size_type& nPosStart, + SwRedlineTable::size_type& nPosEnd, bool bCallDelete); + bool AcceptMovedRedlines(sal_uInt32 nMovedID, bool bCallDelete); + bool RejectMovedRedlines(sal_uInt32 nMovedID, bool bCallDelete); DocumentRedlineManager(DocumentRedlineManager const&) = delete; DocumentRedlineManager& operator=(DocumentRedlineManager const&) = delete; diff --git a/sw/source/core/unocore/unocrsrhelper.cxx b/sw/source/core/unocore/unocrsrhelper.cxx index baac20bbf3d5..70e94360f65c 100644 --- a/sw/source/core/unocore/unocrsrhelper.cxx +++ b/sw/source/core/unocore/unocrsrhelper.cxx @@ -1259,7 +1259,7 @@ void makeRedline( SwPaM const & rPaM, OUString sComment; ::util::DateTime aStamp; uno::Sequence< beans::PropertyValue > aRevertProperties; - bool bIsMoved = false; + sal_uInt32 nMovedID = 0; bool bFoundComment = false; bool bFoundStamp = false; bool bFoundRevertProperties = false; @@ -1277,7 +1277,7 @@ void makeRedline( SwPaM const & rPaM, else if (rProp.Name == "RedlineRevertProperties") bFoundRevertProperties = rProp.Value >>= aRevertProperties; else if (rProp.Name == "RedlineMoved") - rProp.Value >>= bIsMoved; + rProp.Value >>= nMovedID; } SwRedlineData aRedlineData( eType, nAuthor ); @@ -1397,8 +1397,11 @@ void makeRedline( SwPaM const & rPaM, SwRangeRedline* pRedline = new SwRangeRedline( aRedlineData, rPaM ); // set IsMoved bit of the redline to show and handle moved text - if( bIsMoved ) - pRedline->SetMoved(); + if ( nMovedID > 0 ) + { + pRedline->SetMoved( nMovedID ); + rRedlineAccess.GetRedlineTable().setMovedIDIfNeeded(nMovedID); + } RedlineFlags nPrevMode = rRedlineAccess.GetRedlineFlags( ); // xRedlineExtraData is copied here diff --git a/sw/source/core/unocore/unoredline.cxx b/sw/source/core/unocore/unoredline.cxx index b76368c15223..feeceec92b31 100644 --- a/sw/source/core/unocore/unoredline.cxx +++ b/sw/source/core/unocore/unoredline.cxx @@ -250,7 +250,9 @@ uno::Any SwXRedlinePortion::GetPropertyValue( std::u16string_view rPropertyName { aRet <<= rRedline.GetTimeStamp().GetUNODateTime(); } - else if(rPropertyName == UNO_NAME_REDLINE_COMMENT) + else if (rPropertyName == UNO_NAME_REDLINE_MOVED_ID) + aRet <<= rRedline.GetMovedID(); + else if (rPropertyName == UNO_NAME_REDLINE_COMMENT) aRet <<= rRedline.GetComment(); else if(rPropertyName == UNO_NAME_REDLINE_DESCRIPTION) aRet <<= const_cast<SwRangeRedline&>(rRedline).GetDescr(); diff --git a/sw/source/filter/basflt/fltshell.cxx b/sw/source/filter/basflt/fltshell.cxx index fe49700fe2f6..d33cd0a81652 100644 --- a/sw/source/filter/basflt/fltshell.cxx +++ b/sw/source/filter/basflt/fltshell.cxx @@ -704,6 +704,7 @@ void SwFltControlStack::SetAttrInDoc(const SwPosition& rTmpPos, SwRedlineData aData(rFltRedline.m_eType, rFltRedline.m_nAutorNo, rFltRedline.m_aStamp, + 0, OUString(), nullptr ); diff --git a/sw/source/filter/ww8/writerhelper.cxx b/sw/source/filter/ww8/writerhelper.cxx index 86cf8d622200..6746264854cc 100644 --- a/sw/source/filter/ww8/writerhelper.cxx +++ b/sw/source/filter/ww8/writerhelper.cxx @@ -801,7 +801,7 @@ namespace sw (pEntry->m_pAttr.get()); SwRedlineData aData(pFltRedline->m_eType, pFltRedline->m_nAutorNo, - pFltRedline->m_aStamp, OUString(), nullptr); + pFltRedline->m_aStamp, 0, OUString(), nullptr); SwRangeRedline *const pNewRedline(new SwRangeRedline(aData, aRegion)); // the point node may be deleted in AppendRedline, so park diff --git a/sw/source/filter/xml/XMLRedlineImportHelper.cxx b/sw/source/filter/xml/XMLRedlineImportHelper.cxx index df59db44a78d..43ac1cc6769a 100644 --- a/sw/source/filter/xml/XMLRedlineImportHelper.cxx +++ b/sw/source/filter/xml/XMLRedlineImportHelper.cxx @@ -188,6 +188,7 @@ public: OUString sAuthor; // change author string OUString sComment; // change comment string util::DateTime aDateTime; // change DateTime + OUString sMovedID; // change move id string bool bMergeLastParagraph; // the SwRangeRedline::IsDelLastPara flag // each position can may be either empty, an XTextRange, or an SwNodeIndex @@ -369,6 +370,7 @@ void XMLRedlineImportHelper::Add( const OUString& rAuthor, const OUString& rComment, const util::DateTime& rDateTime, + const OUString& rMovedID, bool bMergeLastPara) { // we need to do the following: @@ -406,8 +408,17 @@ void XMLRedlineImportHelper::Add( pInfo->sAuthor = rAuthor; pInfo->sComment = rComment; pInfo->aDateTime = rDateTime; + pInfo->sMovedID = rMovedID; pInfo->bMergeLastParagraph = bMergeLastPara; + //reserve MoveID so it wont be reused by others + if (!rMovedID.isEmpty()) + { + SwDoc* const pDoc(static_cast<SwXMLImport&>(m_rImport).getDoc()); + assert(pDoc); + pDoc->GetDocumentRedlineManager().GetRedlineTable().setMovedIDIfNeeded(rMovedID.toInt32()); + } + // ad 3) auto itPair = m_aRedlineMap.emplace(rId, pInfo); if (itPair.second) @@ -791,6 +802,8 @@ SwRedlineData* XMLRedlineImportHelper::ConvertRedline( aDT.SetSec( pRedlineInfo->aDateTime.Seconds ); aDT.SetNanoSec( pRedlineInfo->aDateTime.NanoSeconds ); + sal_uInt32 nMovedID = pRedlineInfo->sMovedID.toInt32(); + // 3) recursively convert next redline // ( check presence and sanity of hierarchical redline info ) SwRedlineData* pNext = nullptr; @@ -803,7 +816,7 @@ SwRedlineData* XMLRedlineImportHelper::ConvertRedline( // create redline data SwRedlineData* pData = new SwRedlineData(pRedlineInfo->eType, - nAuthorId, aDT, + nAuthorId, aDT, nMovedID, pRedlineInfo->sComment, pNext); // next data (if available) diff --git a/sw/source/filter/xml/XMLRedlineImportHelper.hxx b/sw/source/filter/xml/XMLRedlineImportHelper.hxx index 69e530fbd91e..5bea1badfc90 100644 --- a/sw/source/filter/xml/XMLRedlineImportHelper.hxx +++ b/sw/source/filter/xml/XMLRedlineImportHelper.hxx @@ -81,6 +81,7 @@ public: const OUString& rAuthor, // name of the author const OUString& rComment, // redline comment const css::util::DateTime& rDateTime, // date+time + const OUString& rMovedID, // redline move id bool bMergeLastParagraph); // merge last paragraph? // create a text section for the redline, and return an diff --git a/sw/source/filter/xml/xmltexti.cxx b/sw/source/filter/xml/xmltexti.cxx index 3035e22bce76..fe9d1cb877bc 100644 --- a/sw/source/filter/xml/xmltexti.cxx +++ b/sw/source/filter/xml/xmltexti.cxx @@ -927,12 +927,13 @@ void SwXMLTextImportHelper::RedlineAdd( const OUString& rAuthor, const OUString& rComment, const util::DateTime& rDateTime, + const OUString& rMovedID, bool bMergeLastPara) { // create redline helper on demand OSL_ENSURE(nullptr != m_pRedlineHelper, "helper should have been created in constructor"); if (nullptr != m_pRedlineHelper) - m_pRedlineHelper->Add(rType, rId, rAuthor, rComment, rDateTime, + m_pRedlineHelper->Add(rType, rId, rAuthor, rComment, rDateTime, rMovedID, bMergeLastPara); } diff --git a/sw/source/filter/xml/xmltexti.hxx b/sw/source/filter/xml/xmltexti.hxx index 40d5b169f19d..dbaab53b347d 100644 --- a/sw/source/filter/xml/xmltexti.hxx +++ b/sw/source/filter/xml/xmltexti.hxx @@ -90,6 +90,7 @@ public: const OUString& rAuthor, /// name of the author const OUString& rComment, /// redline comment const css::util::DateTime& rDateTime, /// date+time + const OUString& rMovedID, /// redline move id, to find moveFrom/MoveTo parts bool bMergeLastPara) override; /// merge last paragraph virtual css::uno::Reference<css::text::XTextCursor> RedlineCreateText( css::uno::Reference<css::text::XTextCursor> & rOldCursor, /// needed to get the document |