include/xmloff/txtimp.hxx | 2 include/xmloff/xmltoken.hxx | 1 schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng | 9 sw/inc/IDocumentContentOperations.hxx | 2 sw/inc/crsrsh.hxx | 2 sw/inc/docary.hxx | 8 sw/inc/redline.hxx | 21 sw/inc/unoprnms.hxx | 1 sw/qa/extras/layout/layout2.cxx | 7 sw/qa/extras/uiwriter/data/tdf157663_redlineMove.odt |binary sw/qa/extras/uiwriter/uiwriter5.cxx | 134 +++++ sw/source/core/doc/DocumentContentOperationsManager.cxx | 9 sw/source/core/doc/DocumentRedlineManager.cxx | 201 ++++--- sw/source/core/doc/doccomp.cxx | 4 sw/source/core/doc/docnum.cxx | 8 sw/source/core/doc/docredln.cxx | 302 +++++++++--- sw/source/core/inc/DocumentContentOperationsManager.hxx | 2 sw/source/core/inc/DocumentRedlineManager.hxx | 14 sw/source/core/unocore/unocrsrhelper.cxx | 11 sw/source/core/unocore/unoredline.cxx | 4 sw/source/filter/basflt/fltshell.cxx | 1 sw/source/filter/ww8/writerhelper.cxx | 2 sw/source/filter/xml/XMLRedlineImportHelper.cxx | 15 sw/source/filter/xml/XMLRedlineImportHelper.hxx | 1 sw/source/filter/xml/xmltexti.cxx | 3 sw/source/filter/xml/xmltexti.hxx | 1 writerfilter/source/dmapper/DomainMapper_Impl.cxx | 45 + writerfilter/source/dmapper/DomainMapper_Impl.hxx | 4 xmloff/source/core/xmltoken.cxx | 1 xmloff/source/text/XMLChangeInfoContext.cxx | 7 xmloff/source/text/XMLChangeInfoContext.hxx | 1 xmloff/source/text/XMLChangedRegionImportContext.cxx | 5 xmloff/source/text/XMLChangedRegionImportContext.hxx | 3 xmloff/source/text/XMLRedlineExport.cxx | 9 xmloff/source/text/txtimp.cxx | 1 xmloff/source/token/tokens.txt | 1 36 files changed, 663 insertions(+), 179 deletions(-)
New commits: commit bf5cbc6adfd7cfcd0d59277a8b1899642038ac88 Author: Attila Szűcs <attila.sz...@collabora.com> AuthorDate: Tue Oct 17 09:31:22 2023 +0200 Commit: Caolán McNamara <caolan.mcnam...@collabora.com> CommitDate: Wed Oct 25 09:43:16 2023 +0200 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 <jenkinscollaboraoff...@gmail.com> Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com> diff --git a/include/xmloff/txtimp.hxx b/include/xmloff/txtimp.hxx index 218db6fa7acd..0320c5360aa6 100644 --- a/include/xmloff/txtimp.hxx +++ b/include/xmloff/txtimp.hxx @@ -381,6 +381,8 @@ public: const OUString& rComment, /// date+time const css::util::DateTime& rDateTime, + /// move id, to find other parts (moveFrom/MoveTo) + const OUString& rMoveId, /// merge last paras bool bMergeLastParagraph); diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx index 1fb203e3469c..693ebd728f6f 100644 --- a/include/xmloff/xmltoken.hxx +++ b/include/xmloff/xmltoken.hxx @@ -1344,6 +1344,7 @@ namespace xmloff::token { XML_MOVE_FROM_LEFT, XML_MOVE_FROM_RIGHT, XML_MOVE_FROM_TOP, + XML_MOVE_ID, XML_MOVE_PROTECT, XML_MOVE_SHORT, XML_MOVEMENT, diff --git a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng index c02dcf8925be..d0a7c223399c 100644 --- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng +++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng @@ -3648,6 +3648,15 @@ xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1. </rng:optional> </rng:define> + <!-- TODO(aszucs) no proposal - unique identifier for move redline --> + <rng:define name="office-change-info" combine="interleave"> + <rng:optional> + <rng:attribute name="loext:move-id"> + <rng:ref name="integer"/> + </rng:attribute> + </rng:optional> + </rng:define> + <!-- Belongs to project MCGR (Armin Le Grand) LO 7.6 Intended to be used for theme colors too --> <rng:define name="common-complex-color-attributes"> diff --git a/sw/inc/IDocumentContentOperations.hxx b/sw/inc/IDocumentContentOperations.hxx index 5a95d0ba95b8..def239b17414 100644 --- a/sw/inc/IDocumentContentOperations.hxx +++ b/sw/inc/IDocumentContentOperations.hxx @@ -133,7 +133,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 4694ae9ca578..36196165078a 100644 --- a/sw/inc/crsrsh.hxx +++ b/sw/inc/crsrsh.hxx @@ -165,7 +165,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 98c84cbbd270..4ce47340670c 100644 --- a/sw/inc/docary.hxx +++ b/sw/inc/docary.hxx @@ -226,6 +226,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(); } @@ -262,6 +263,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 8dfb81fd140b..e0951f4101df 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; @@ -177,7 +178,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! @@ -216,7 +217,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) @@ -280,8 +282,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 31b032c1d59b..ab1c40ccecdd 100644 --- a/sw/inc/unoprnms.hxx +++ b/sw/inc/unoprnms.hxx @@ -594,6 +594,7 @@ inline constexpr OUStringLiteral UNO_NAME_GRAPHIC_IS_INVERTED = u"GraphicIsInver inline constexpr OUStringLiteral UNO_NAME_TRANSPARENCY = u"Transparency"; inline constexpr OUStringLiteral UNO_NAME_REDLINE_AUTHOR = u"RedlineAuthor"; inline constexpr OUStringLiteral UNO_NAME_REDLINE_DATE_TIME = u"RedlineDateTime"; +inline constexpr OUStringLiteral UNO_NAME_REDLINE_MOVED_ID = u"RedlineMovedID"; inline constexpr OUStringLiteral UNO_NAME_REDLINE_COMMENT = u"RedlineComment"; inline constexpr OUStringLiteral UNO_NAME_REDLINE_DESCRIPTION = u"RedlineDescription"; inline constexpr OUStringLiteral UNO_NAME_REDLINE_TYPE = u"RedlineType"; diff --git a/sw/qa/extras/layout/layout2.cxx b/sw/qa/extras/layout/layout2.cxx index bce3121ab3d0..27072238dc5c 100644 --- a/sw/qa/extras/layout/layout2.cxx +++ b/sw/qa/extras/layout/layout2.cxx @@ -755,10 +755,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 new file mode 100644 index 000000000000..84b8adadb090 Binary files /dev/null and b/sw/qa/extras/uiwriter/data/tdf157663_redlineMove.odt differ diff --git a/sw/qa/extras/uiwriter/uiwriter5.cxx b/sw/qa/extras/uiwriter/uiwriter5.cxx index 691d9c1f05be..b063b7fa0ee1 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 @@ -2314,6 +2315,139 @@ 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()); + + // Check if there are no more move redlines + for (SwRedlineTable::size_type i = 0; i < rTable.size(); i++) + { + CPPUNIT_ASSERT(rTable[i]->GetMoved() == 0); + } + + // 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(rTable[7]->GetMoved() == 0); // "e " deleted text not moved + CPPUNIT_ASSERT(rTable[8]->GetMoved() == nMovedID); // "ent" + CPPUNIT_ASSERT(rTable[9]->GetMoved() == 0); // " t" + CPPUNIT_ASSERT(rTable[10]->GetMoved() == nMovedID); // "teen" + // moved text to here + CPPUNIT_ASSERT(rTable[11]->GetMoved() == nMovedID); // "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(rTable[13]->GetMoved() == nMovedID); // "Eigen" + CPPUNIT_ASSERT(rTable[14]->GetMoved() == nMovedID); // "hte" + CPPUNIT_ASSERT(rTable[15]->GetMoved() == nMovedID); // "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 d467fd2c5789..51314f1a7fe1 100644 --- a/sw/source/core/doc/DocumentContentOperationsManager.cxx +++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx @@ -2005,9 +2005,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(); @@ -2075,7 +2075,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 102e3ec1c780..0a5367e89107 100644 --- a/sw/source/core/doc/DocumentRedlineManager.cxx +++ b/sw/source/core/doc/DocumentRedlineManager.cxx @@ -53,8 +53,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 ) @@ -1887,6 +1888,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; @@ -2847,69 +2854,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--; @@ -2957,6 +2916,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) { @@ -2988,13 +2977,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 { @@ -3131,20 +3130,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--; @@ -3202,6 +3202,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) { @@ -3233,14 +3267,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 1779ec998300..a4e259403afa 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 703c15f2187d..75d8ebf534a0 100644 --- a/sw/source/core/doc/docnum.cxx +++ b/sw/source/core/doc/docnum.cxx @@ -2171,7 +2171,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 @@ -2205,7 +2207,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); @@ -2265,7 +2267,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 ad66c7cba391..95498d625c90 100644 --- a/sw/source/core/doc/docredln.cxx +++ b/sw/source/core/doc/docredln.cxx @@ -339,6 +339,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) { @@ -753,7 +759,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; @@ -770,74 +897,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 ) @@ -1010,10 +1196,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 ); } @@ -1029,15 +1215,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) { } @@ -1065,7 +1251,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 ))) && @@ -1082,7 +1268,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 )); @@ -1122,9 +1308,10 @@ OUString SwRedlineData::GetDescr() const 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); @@ -1967,7 +2154,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 bb857804f277..0ba770f62f98 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 04567219168f..6166c1e6c88d 100644 --- a/sw/source/core/unocore/unocrsrhelper.cxx +++ b/sw/source/core/unocore/unocrsrhelper.cxx @@ -1263,7 +1263,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; @@ -1281,7 +1281,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 ); @@ -1401,8 +1401,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 c32180a9bf71..b0c0a0a5f8dd 100644 --- a/sw/source/core/unocore/unoredline.cxx +++ b/sw/source/core/unocore/unoredline.cxx @@ -257,7 +257,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 fbfec5eaeccf..b2624c7907f0 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 5a45a6dbb04b..3c516d00aa70 100644 --- a/sw/source/filter/ww8/writerhelper.cxx +++ b/sw/source/filter/ww8/writerhelper.cxx @@ -812,7 +812,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 6d3bb6007ae6..2d6821edfb84 100644 --- a/sw/source/filter/xml/XMLRedlineImportHelper.cxx +++ b/sw/source/filter/xml/XMLRedlineImportHelper.cxx @@ -193,6 +193,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 @@ -374,6 +375,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: @@ -411,8 +413,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) @@ -796,6 +807,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; @@ -808,7 +821,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 7ec4616f76dd..84fc817c09cd 100644 --- a/sw/source/filter/xml/xmltexti.cxx +++ b/sw/source/filter/xml/xmltexti.cxx @@ -942,12 +942,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 diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx index d3e77b15a42c..722b616ac4d2 100644 --- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx +++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx @@ -352,6 +352,7 @@ DomainMapper_Impl::DomainMapper_Impl( m_bStartBibliography(false), m_nStartGenericField(0), m_bTextInserted(false), + m_nLastRedlineMovedID(1), m_sCurrentPermId(0), m_bFrameDirectionSet(false), m_bInDocDefaultsImport(false), @@ -3563,8 +3564,42 @@ void DomainMapper_Impl::CreateRedline(uno::Reference<text::XTextRange> const& xR pRedlineProperties[1].Value <<= aDateTime; pRedlineProperties[2].Name = getPropertyName( PROP_REDLINE_REVERT_PROPERTIES ); pRedlineProperties[2].Value <<= pRedline->m_aRevertProperties; + + sal_uInt32 nRedlineMovedID = 0; + if (bRedlineMoved) + { + if (!m_sCurrentBkmkId.isEmpty()) + { + nRedlineMovedID = 1; + BookmarkMap_t::iterator aBookmarkIter = m_aBookmarkMap.find(m_sCurrentBkmkId); + if (aBookmarkIter != m_aBookmarkMap.end()) + { + OUString sMoveID = aBookmarkIter->second.m_sBookmarkName; + auto aIter = m_aRedlineMoveIDs.end(); + + if (sMoveID.indexOf("__RefMoveFrom__") >= 0) + { + aIter = std::find(m_aRedlineMoveIDs.begin(), m_aRedlineMoveIDs.end(), + sMoveID.subView(15)); + } + else if (sMoveID.indexOf("__RefMoveTo__") >= 0) + { + aIter = std::find(m_aRedlineMoveIDs.begin(), m_aRedlineMoveIDs.end(), + sMoveID.subView(13)); + }; + + if (aIter != m_aRedlineMoveIDs.end()) + { + nRedlineMovedID = aIter - m_aRedlineMoveIDs.begin() + 2; + m_nLastRedlineMovedID = nRedlineMovedID; + } + } + } + else + nRedlineMovedID = m_nLastRedlineMovedID; + } pRedlineProperties[3].Name = "RedlineMoved"; - pRedlineProperties[3].Value <<= bRedlineMoved; + pRedlineProperties[3].Value <<= nRedlineMovedID; if (!m_bIsActualParagraphFramed) { @@ -8278,6 +8313,14 @@ void DomainMapper_Impl::SetBookmarkName( const OUString& rBookmarkName ) } } + if ((m_sCurrentBkmkPrefix.equals("__RefMoveFrom__") + || m_sCurrentBkmkPrefix.equals("__RefMoveTo__")) + && std::find(m_aRedlineMoveIDs.begin(), m_aRedlineMoveIDs.end(), rBookmarkName) + == m_aRedlineMoveIDs.end()) + { + m_aRedlineMoveIDs.push_back(rBookmarkName); + } + aBookmarkIter->second.m_sBookmarkName = m_sCurrentBkmkPrefix + rBookmarkName; m_sCurrentBkmkPrefix.clear(); } diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx b/writerfilter/source/dmapper/DomainMapper_Impl.hxx index c188ab828380..5c2a128a2b11 100644 --- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx +++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx @@ -501,6 +501,10 @@ private: bool m_bTextInserted; LineNumberSettings m_aLineNumberSettings; + std::vector<OUString> m_aRedlineMoveIDs; + // Remember the last used redline MoveID. To avoid regression, because of wrong docx export + sal_uInt32 m_nLastRedlineMovedID; + BookmarkMap_t m_aBookmarkMap; OUString m_sCurrentBkmkId; OUString m_sCurrentBkmkName; diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx index 230a1bde8e12..4c4bde854a9b 100644 --- a/xmloff/source/core/xmltoken.cxx +++ b/xmloff/source/core/xmltoken.cxx @@ -1357,6 +1357,7 @@ namespace xmloff::token { TOKEN( "move-from-left", XML_MOVE_FROM_LEFT ), TOKEN( "move-from-right", XML_MOVE_FROM_RIGHT ), TOKEN( "move-from-top", XML_MOVE_FROM_TOP ), + TOKEN( "move-id", XML_MOVE_ID ), TOKEN( "move-protect", XML_MOVE_PROTECT ), TOKEN( "move-short", XML_MOVE_SHORT ), TOKEN( "movement", XML_MOVEMENT ), diff --git a/xmloff/source/text/XMLChangeInfoContext.cxx b/xmloff/source/text/XMLChangeInfoContext.cxx index 9dddbad72a67..2273b1766965 100644 --- a/xmloff/source/text/XMLChangeInfoContext.cxx +++ b/xmloff/source/text/XMLChangeInfoContext.cxx @@ -61,6 +61,9 @@ css::uno::Reference< css::xml::sax::XFastContextHandler > XMLChangeInfoContext:: case XML_ELEMENT(DC, XML_DATE): xContext = new XMLStringBufferImportContext(GetImport(), sDateTimeBuffer); break; + case XML_ELEMENT(LO_EXT, XML_MOVE_ID): + xContext = new XMLStringBufferImportContext(GetImport(), sMovedIDBuffer); + break; case XML_ELEMENT(TEXT, XML_P): case XML_ELEMENT(LO_EXT, XML_P): xContext = new XMLStringBufferImportContext(GetImport(), sCommentBuffer); @@ -76,8 +79,8 @@ void XMLChangeInfoContext::endFastElement(sal_Int32 ) { // set values at changed region context rChangedRegion.SetChangeInfo(rType, sAuthorBuffer.makeStringAndClear(), - sCommentBuffer.makeStringAndClear(), - sDateTimeBuffer); + sCommentBuffer.makeStringAndClear(), sDateTimeBuffer, + sMovedIDBuffer.makeStringAndClear()); sDateTimeBuffer.setLength(0); } diff --git a/xmloff/source/text/XMLChangeInfoContext.hxx b/xmloff/source/text/XMLChangeInfoContext.hxx index 04f055d533a4..994e285d0463 100644 --- a/xmloff/source/text/XMLChangeInfoContext.hxx +++ b/xmloff/source/text/XMLChangeInfoContext.hxx @@ -43,6 +43,7 @@ class XMLChangeInfoContext : public SvXMLImportContext OUStringBuffer sAuthorBuffer; OUStringBuffer sDateTimeBuffer; + OUStringBuffer sMovedIDBuffer; OUStringBuffer sCommentBuffer; XMLChangedRegionImportContext& rChangedRegion; diff --git a/xmloff/source/text/XMLChangedRegionImportContext.cxx b/xmloff/source/text/XMLChangedRegionImportContext.cxx index fe00c4a058b9..ecf5a63d8dab 100644 --- a/xmloff/source/text/XMLChangedRegionImportContext.cxx +++ b/xmloff/source/text/XMLChangedRegionImportContext.cxx @@ -140,13 +140,14 @@ void XMLChangedRegionImportContext::SetChangeInfo( const OUString& rType, const OUString& rAuthor, const OUString& rComment, - std::u16string_view rDate) + std::u16string_view rDate, + const OUString& rMovedID) { util::DateTime aDateTime; if (::sax::Converter::parseDateTime(aDateTime, rDate)) { GetImport().GetTextImport()->RedlineAdd( - rType, sID, rAuthor, rComment, aDateTime, bMergeLastPara); + rType, sID, rAuthor, rComment, aDateTime, rMovedID, bMergeLastPara); } } diff --git a/xmloff/source/text/XMLChangedRegionImportContext.hxx b/xmloff/source/text/XMLChangedRegionImportContext.hxx index db0e48a43d84..e05e97c93227 100644 --- a/xmloff/source/text/XMLChangedRegionImportContext.hxx +++ b/xmloff/source/text/XMLChangedRegionImportContext.hxx @@ -70,7 +70,8 @@ public: void SetChangeInfo(const OUString& rType, const OUString& rAuthor, const OUString& rComment, - std::u16string_view rDate); + std::u16string_view rDate, + const OUString& rMovedId); /// create redline XText/XTextCursor on demand and register with /// XMLTextImportHelper diff --git a/xmloff/source/text/XMLRedlineExport.cxx b/xmloff/source/text/XMLRedlineExport.cxx index aa9b364fedb4..fe611ae48340 100644 --- a/xmloff/source/text/XMLRedlineExport.cxx +++ b/xmloff/source/text/XMLRedlineExport.cxx @@ -454,6 +454,15 @@ void XMLRedlineExport::ExportChangeInfo( : sTmp ); } + aAny = rPropSet->getPropertyValue("RedlineMovedID"); + sal_uInt32 nTmp(0); + aAny >>= nTmp; + if (nTmp > 1) + { + SvXMLElementExport aCreatorElem(rExport, XML_NAMESPACE_LO_EXT, XML_MOVE_ID, true, false); + rExport.Characters( OUString::number( nTmp ) ); + } + aAny = rPropSet->getPropertyValue("RedlineDateTime"); util::DateTime aDateTime; aAny >>= aDateTime; diff --git a/xmloff/source/text/txtimp.cxx b/xmloff/source/text/txtimp.cxx index e0df349b63e4..7071f62e260c 100644 --- a/xmloff/source/text/txtimp.cxx +++ b/xmloff/source/text/txtimp.cxx @@ -2316,6 +2316,7 @@ void XMLTextImportHelper::RedlineAdd( const OUString& /*rType*/, const OUString& /*rAuthor*/, const OUString& /*rComment*/, const util::DateTime& /*rDateTime*/, + const OUString& /*rMovedID*/, bool /*bMergeLastPara*/) { // dummy implementation: do nothing diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt index 869c47567f5d..7417b6cae671 100644 --- a/xmloff/source/token/tokens.txt +++ b/xmloff/source/token/tokens.txt @@ -1257,6 +1257,7 @@ move-from-bottom move-from-left move-from-right move-from-top +move-id move-protect move-short movement