sw/inc/redline.hxx | 11 ++- sw/qa/extras/layout/data/tdf145719.odt |binary sw/qa/extras/layout/layout2.cxx | 16 +++++ sw/source/core/doc/docredln.cxx | 100 +++++++++++++++++++++------------ sw/source/core/inc/UndoCore.hxx | 1 sw/source/core/undo/undobj.cxx | 4 - 6 files changed, 93 insertions(+), 39 deletions(-)
New commits: commit 7bc57e698910e24495605bd197a6d3ab5e0be5b8 Author: László Németh <nem...@numbertext.org> AuthorDate: Fri Nov 19 12:57:23 2021 +0100 Commit: László Németh <nem...@numbertext.org> CommitDate: Tue Nov 23 11:54:13 2021 +0100 tdf#145719 sw: track moved text in import and ChangesInMargin Recognize moved text by accessing to the hidden redline content pContentSect during ODT import (in the case of Delete redlines) and ChangesInMargin mode (Delete or Insert redlines depending on Deletion in Margin or Insertion in Margin modes). Fix Undo and redline stack handling by moving IsMoved bit to SwRedlineData from SwRangeRedline. Note: .fodt format is applicable for the unit test document, because it's not affected by the problem. Change-Id: Ifd4f993520bec4b845d978a844c465509ea87b50 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/125552 Tested-by: László Németh <nem...@numbertext.org> Reviewed-by: László Németh <nem...@numbertext.org> diff --git a/sw/inc/redline.hxx b/sw/inc/redline.hxx index 8d17948205fa..5e01cfa8349f 100644 --- a/sw/inc/redline.hxx +++ b/sw/inc/redline.hxx @@ -95,6 +95,7 @@ class SW_DLLPUBLIC SwRedlineData RedlineType m_eType; sal_uInt16 m_nSeqNo; bool m_bAutoFormat; + bool m_bMoved; public: SwRedlineData( RedlineType eT, std::size_t nAut ); @@ -111,6 +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_sComment == rCmp.m_sComment && (( !m_pNext && !rCmp.m_pNext ) || ( m_pNext && rCmp.m_pNext && *m_pNext == *rCmp.m_pNext )) && @@ -133,6 +135,8 @@ public: void SetAutoFormat() { m_bAutoFormat = true; } bool IsAutoFormat() const { return m_bAutoFormat; } + void SetMoved() { m_bMoved = true; } + bool IsMoved() const { return m_bMoved; } bool CanCombine( const SwRedlineData& rCmp ) const; // ExtraData gets copied, the pointer is therefore not taken over by @@ -155,7 +159,6 @@ class SW_DLLPUBLIC SwRangeRedline final : public SwPaM SwNodeIndex* m_pContentSect; bool m_bDelLastPara : 1; bool m_bIsVisible : 1; - bool m_bIsMoved : 1; sal_uInt32 m_nId; std::optional<tools::Long> m_oLOKLastNodeTop; @@ -175,7 +178,7 @@ public: SwRangeRedline(SwRedlineData* pData, const SwPosition& rPos, bool bDelLP) : SwPaM( rPos ), m_pRedlineData( pData ), m_pContentSect( nullptr ), - m_bDelLastPara( bDelLP ), m_bIsVisible( true ), m_bIsMoved( false ), m_nId( s_nLastId++ ) + m_bDelLastPara( bDelLP ), m_bIsVisible( true ), m_nId( s_nLastId++ ) {} SwRangeRedline( const SwRangeRedline& ); virtual ~SwRangeRedline() override; @@ -263,8 +266,8 @@ public: void MaybeNotifyRedlinePositionModification(tools::Long nTop); - void SetMoved() { m_bIsMoved = true; } - bool IsMoved() const { return m_bIsMoved; } + void SetMoved() { m_pRedlineData->SetMoved(); } + bool IsMoved() const { return m_pRedlineData->IsMoved(); } }; void MaybeNotifyRedlineModification(SwRangeRedline& rRedline, SwDoc& rDoc); diff --git a/sw/qa/extras/layout/data/tdf145719.odt b/sw/qa/extras/layout/data/tdf145719.odt new file mode 100644 index 000000000000..62e4cc4a73e4 Binary files /dev/null and b/sw/qa/extras/layout/data/tdf145719.odt differ diff --git a/sw/qa/extras/layout/layout2.cxx b/sw/qa/extras/layout/layout2.cxx index 9eee8228ea8d..3ca1abc2747a 100644 --- a/sw/qa/extras/layout/layout2.cxx +++ b/sw/qa/extras/layout/layout2.cxx @@ -350,6 +350,22 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testRedlineMovingDOCX) assertXPath(pXmlDoc, "/metafile/push/push/push/textcolor[@color='#008000']", 6); } +CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf145719) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf145719.odt"); + SwDocShell* pShell = pDoc->GetDocShell(); + + // Dump the rendering of the first page as an XML file. + std::shared_ptr<GDIMetaFile> xMetaFile = pShell->GetPreviewMetaFile(); + MetafileXmlDump dumper; + xmlDocUniquePtr pXmlDoc = dumpAndParse(dumper, *xMetaFile); + CPPUNIT_ASSERT(pXmlDoc); + + // text colors show moved text + // This was 0 (other color, not COL_GREEN, color of the tracked text movement) + assertXPath(pXmlDoc, "/metafile/push/push/push/textcolor[@color='#008000']", 4); +} + CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf145225_RedlineMovingWithBadInsertion) { SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf42748.fodt"); diff --git a/sw/source/core/doc/docredln.cxx b/sw/source/core/doc/docredln.cxx index 732d92a40246..d911ecb85d53 100644 --- a/sw/source/core/doc/docredln.cxx +++ b/sw/source/core/doc/docredln.cxx @@ -772,6 +772,7 @@ const SwRangeRedline* SwRedlineTable::FindAtPosition( const SwPosition& rSttPos, bool SwRedlineTable::isMoved( size_type rPos ) const { + bool bRet = false; auto constexpr nLookahead = 20; SwRangeRedline* pRedline = (*this)[ rPos ]; @@ -785,43 +786,79 @@ bool SwRedlineTable::isMoved( size_type rPos ) const // only deleted or inserted text can be moved return false; - // Skip terminating white spaces of the redline, a workaround - // for a typical difference resulted by e.g. Writer UX: - // selecting a sentence or a word, and moving it with - // the mouse, Writer removes a space at the deletion - // to avoid double spaces, also inserts a space at - // the insertion point automatically. Because the result - // can be different (space before and space after the - // moved text), compare the redlines without terminating spaces - const OUString sTrimmed = pRedline->GetText().trim(); + bool bDeletePaM = false; + SwPaM* pPaM; + + // 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 + { + SwNodeIndex aTmpIdx( *pRedline->GetContentIdx()->GetNode().EndOfSectionNode() ); + pPaM = new SwPaM(*pRedline->GetContentIdx(), aTmpIdx ); + bDeletePaM = true; + } + + const OUString sTrimmed = pPaM->GetText().trim(); if ( sTrimmed.isEmpty() ) + { + if ( bDeletePaM ) + delete pPaM; return false; + } // search pair around the actual redline size_type nEnd = rPos + nLookahead < size() ? rPos + nLookahead : size(); rPos = rPos > nLookahead ? rPos - nLookahead : 0; - for ( ; rPos < nEnd ; ++rPos ) + for ( ; rPos < nEnd && !bRet ; ++rPos ) { SwRangeRedline* pPair = (*this)[ rPos ]; - // TODO handle also Show Changes in Margin mode - if ( pPair->HasMark() && pPair->IsVisible() ) + + // redline must be the requested type and from the same author + if ( nPairType != pPair->GetType() || + pRedline->GetAuthor() != pPair->GetAuthor() ) { - // pair at tracked moving: same text from the same author, but with opposite type - if ( nPairType == pPair->GetType() && - pRedline->GetAuthor() == pPair->GetAuthor() && - abs(pRedline->GetText().getLength() - pPair->GetText().getLength()) <= 2 && - sTrimmed == pPair->GetText().trim() ) - { - pRedline->SetMoved(); - pPair->SetMoved(); - pPair->InvalidateRange(SwRangeRedline::Invalidation::Remove); - return true; - } + continue; } + + bool bDeletePairPaM = false; + SwPaM* pPairPaM; + + // 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 + SwNodeIndex aTmpIdx( *pPair->GetContentIdx()->GetNode().EndOfSectionNode() ); + pPairPaM = new SwPaM(*pPair->GetContentIdx(), aTmpIdx ); + bDeletePairPaM = true; + } + + // pair at tracked moving: same text by trimming terminatin white spaces + if ( abs(pPaM->GetText().getLength() - pPairPaM->GetText().getLength()) <= 2 && + sTrimmed == pPairPaM->GetText().trim() ) + { + pRedline->SetMoved(); + pPair->SetMoved(); + pPair->InvalidateRange(SwRangeRedline::Invalidation::Remove); + bRet = true; + } + + if ( bDeletePairPaM ) + delete pPairPaM; } - return false; + + if ( bDeletePaM ) + delete pPaM; + + return bRet; } void SwRedlineTable::dumpAsXml(xmlTextWriterPtr pWriter) const @@ -987,7 +1024,7 @@ bool SwRedlineExtraData_Format::operator == ( const SwRedlineExtraData& rCmp ) c SwRedlineData::SwRedlineData( RedlineType eT, std::size_t nAut ) : m_pNext( nullptr ), m_pExtraData( nullptr ), m_aStamp( DateTime::SYSTEM ), - m_nAuthor( nAut ), m_eType( eT ), m_nSeqNo( 0 ), m_bAutoFormat(false) + m_nAuthor( nAut ), m_eType( eT ), m_nSeqNo( 0 ), m_bAutoFormat(false), m_bMoved(false) { m_aStamp.SetNanoSec( 0 ); } @@ -1003,6 +1040,7 @@ SwRedlineData::SwRedlineData( , m_eType( rCpy.m_eType ) , m_nSeqNo( rCpy.m_nSeqNo ) , m_bAutoFormat(false) + , m_bMoved( rCpy.m_bMoved ) { } @@ -1010,7 +1048,7 @@ SwRedlineData::SwRedlineData( SwRedlineData::SwRedlineData(RedlineType eT, std::size_t nAut, const DateTime& rDT, const OUString& rCmnt, SwRedlineData *pNxt) : m_pNext(pNxt), m_pExtraData(nullptr), m_sComment(rCmnt), m_aStamp(rDT), - m_nAuthor(nAut), m_eType(eT), m_nSeqNo(0), m_bAutoFormat(false) + m_nAuthor(nAut), m_eType(eT), m_nSeqNo(0), m_bAutoFormat(false), m_bMoved(false) { } @@ -1030,6 +1068,7 @@ bool SwRedlineData::CanCombine(const SwRedlineData& rCmp) const m_eType == rCmp.m_eType && m_sComment == rCmp.m_sComment && aTime == aCompareTime && + m_bMoved == rCmp.m_bMoved && (( !m_pNext && !rCmp.m_pNext ) || ( m_pNext && rCmp.m_pNext && m_pNext->CanCombine( *rCmp.m_pNext ))) && @@ -1080,7 +1119,6 @@ SwRangeRedline::SwRangeRedline(RedlineType eTyp, const SwPaM& rPam ) { m_bDelLastPara = false; m_bIsVisible = true; - m_bIsMoved = false; if( !rPam.HasMark() ) DeleteMark(); } @@ -1093,7 +1131,6 @@ SwRangeRedline::SwRangeRedline( const SwRedlineData& rData, const SwPaM& rPam ) { m_bDelLastPara = false; m_bIsVisible = true; - m_bIsMoved = false; if( !rPam.HasMark() ) DeleteMark(); } @@ -1106,7 +1143,6 @@ SwRangeRedline::SwRangeRedline( const SwRedlineData& rData, const SwPosition& rP { m_bDelLastPara = false; m_bIsVisible = true; - m_bIsMoved = false; } SwRangeRedline::SwRangeRedline( const SwRangeRedline& rCpy ) @@ -1117,9 +1153,6 @@ SwRangeRedline::SwRangeRedline( const SwRangeRedline& rCpy ) { m_bDelLastPara = false; m_bIsVisible = true; - // inserting characters within a moved text must keep - // IsMoved bit in the second part of the split redline - m_bIsMoved = rCpy.IsMoved(); if( !rCpy.HasMark() ) DeleteMark(); } @@ -1840,8 +1873,7 @@ void SwRangeRedline::SetContentIdx( const SwNodeIndex* pIdx ) bool SwRangeRedline::CanCombine( const SwRangeRedline& rRedl ) const { return IsVisible() && rRedl.IsVisible() && - m_pRedlineData->CanCombine( *rRedl.m_pRedlineData ) && - !IsMoved() && !rRedl.IsMoved(); + m_pRedlineData->CanCombine( *rRedl.m_pRedlineData ); } void SwRangeRedline::PushData( const SwRangeRedline& rRedl, bool bOwnAsNext ) diff --git a/sw/source/core/inc/UndoCore.hxx b/sw/source/core/inc/UndoCore.hxx index 7e670c9c6ba4..6efaf23c0b61 100644 --- a/sw/source/core/inc/UndoCore.hxx +++ b/sw/source/core/inc/UndoCore.hxx @@ -62,6 +62,7 @@ public: #if OSL_DEBUG_LEVEL > 0 sal_uInt16 m_nRedlineCount; + bool m_bRedlineMoved; #endif }; diff --git a/sw/source/core/undo/undobj.cxx b/sw/source/core/undo/undobj.cxx index b03a3e1daded..64faed602dae 100644 --- a/sw/source/core/undo/undobj.cxx +++ b/sw/source/core/undo/undobj.cxx @@ -1367,6 +1367,7 @@ SwRedlineSaveData::SwRedlineSaveData( #if OSL_DEBUG_LEVEL > 0 m_nRedlineCount = rSttPos.nNode.GetNode().GetDoc().getIDocumentRedlineAccess().GetRedlineTable().size(); + m_bRedlineMoved = rRedl.IsMoved(); #endif } @@ -1481,7 +1482,8 @@ void SwUndo::SetSaveData( SwDoc& rDoc, SwRedlineSaveDatas& rSData ) #if OSL_DEBUG_LEVEL > 0 // check redline count against count saved in RedlineSaveData object - assert(rSData.empty() || + // except in the case of moved redlines + assert(rSData.empty() || rSData[0].m_bRedlineMoved || (rSData[0].m_nRedlineCount == rDoc.getIDocumentRedlineAccess().GetRedlineTable().size())); // "redline count not restored properly" #endif