sw/inc/fesh.hxx                      |    3 -
 sw/qa/core/edit/edit.cxx             |   77 +++++++++++++++++++++++++++++++++++
 sw/source/core/edit/edredln.cxx      |   25 ++++++++++-
 sw/source/core/frmedt/fecopy.cxx     |    7 ++-
 sw/source/uibase/dochdl/swdtflvr.cxx |   12 ++---
 sw/source/uibase/inc/swdtflvr.hxx    |    4 -
 6 files changed, 114 insertions(+), 14 deletions(-)

New commits:
commit db541619cb1ca83598ec479eb9f52e559a8fe72d
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Mon Mar 24 08:40:40 2025 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Mon Mar 24 16:37:43 2025 +0100

    cool#11357 sw redline reinstate: handle a single rich delete
    
    In case the cursor is inside a delete redline (single-paragraph, plain
    text), then .uno:ReinstateTrackedChange works for that change. If the
    change contains formatting, then that formatting is not copied to the
    insertion.
    
    This is simply because SwEditShell::ReinstatePaM() gets the content of
    the delete redline as an OUString. Doing a rich copy would preserve
    formatting, but then
    sw::DocumentContentOperationsManager::CopyImplImpl() would use
    lcl_DeleteRedlines() to strip away content which is inside a delete
    redline, so that won't work out of the box, either.
    
    Fix the problem by copying the content of the delete redline to a
    clipboard doc and inserting from there, and by introducing a new
    bDeleteRedlines flag (defaults to true). When that flag is off, the copy
    does what we need here: it preserves formatting, but doesn't strip away
    content which was originally inside a delete redline.
    
    Go via SwTransferable instead of invoking
    sw::DocumentContentOperationsManager::CopyRange() directly, because this
    way the inserted content will be inside an insert redline & we get
    working undo/redo out of the box.
    
    Change-Id: Id853844b7282c6b1c36cb0f4f8cc75d968e53dcd
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/183270
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins

diff --git a/sw/inc/fesh.hxx b/sw/inc/fesh.hxx
index e5c114474425..6b5dd4e47707 100644
--- a/sw/inc/fesh.hxx
+++ b/sw/inc/fesh.hxx
@@ -256,7 +256,8 @@ public:
     virtual ~SwFEShell() override;
 
     /// Copy and Paste methods for internal clipboard.
-    SW_DLLPUBLIC void Copy( SwDoc& rClpDoc, const OUString* pNewClpText = 
nullptr );
+    /// bDeleteRedlines: if content inside a delete redline should be stripped 
away in rClpDoc.
+    SW_DLLPUBLIC void Copy( SwDoc& rClpDoc, const OUString* pNewClpText = 
nullptr, bool bDeleteRedlines = true );
     SW_DLLPUBLIC bool Paste( SwDoc& rClpDoc, bool bNestedTable = false );
 
     /// Paste some pages into another doc - used in mailmerge.
diff --git a/sw/qa/core/edit/edit.cxx b/sw/qa/core/edit/edit.cxx
index 581fdc3af456..21455842da24 100644
--- a/sw/qa/core/edit/edit.cxx
+++ b/sw/qa/core/edit/edit.cxx
@@ -9,6 +9,8 @@
 
 #include <swmodeltestbase.hxx>
 
+#include <editeng/wghtitem.hxx>
+
 #include <docsh.hxx>
 #include <view.hxx>
 #include <wrtsh.hxx>
@@ -16,6 +18,7 @@
 #include <IDocumentRedlineAccess.hxx>
 #include <swmodule.hxx>
 #include <redline.hxx>
+#include <ndtxt.hxx>
 
 namespace
 {
@@ -205,6 +208,80 @@ CPPUNIT_TEST_FIXTURE(Test, 
testRedlineReinstateSinglePlainDelete)
     CPPUNIT_ASSERT_EQUAL(RedlineType::Insert, rRedlineData2.GetType());
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testRedlineReinstateSingleRichDelete)
+{
+    // Given a document: a<del>b<b>c
d</b>e</del>f:
+    createSwDoc();
+    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
+    pWrtShell->Insert("abc");
+    pWrtShell->SplitNode();
+    pWrtShell->Insert("def");
+    SwModule* pModule = SwModule::get();
+    // Mark cd as bold:
+    pWrtShell->SttPara(/*bSelect=*/false);
+    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 2, 
/*bBasicCall=*/false);
+    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 3, 
/*bBasicCall=*/false);
+    SwPaM* pCursor = pWrtShell->GetCursor();
+    CPPUNIT_ASSERT_EQUAL(u"c
d"_ustr, pCursor->GetText());
+    SwView& rView = pWrtShell->GetView();
+    {
+        SvxWeightItem aWeightItem(WEIGHT_BOLD, RES_CHRATR_WEIGHT);
+        SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> 
aSet(rView.GetPool());
+        aSet.Put(aWeightItem);
+        pWrtShell->SetAttrSet(aSet);
+    }
+    pModule->SetRedlineAuthor("Alice");
+    RedlineFlags nMode = pWrtShell->GetRedlineFlags();
+    pWrtShell->SetRedlineFlags(nMode | RedlineFlags::On);
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, 
/*bBasicCall=*/false);
+    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 5, 
/*bBasicCall=*/false);
+    pWrtShell->DelRight();
+    pWrtShell->SetRedlineFlags(nMode);
+
+    // When a 2nd user reinstates that change:
+    pModule->SetRedlineAuthor("Bob");
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 2, 
/*bBasicCall=*/false);
+    dispatchCommand(mxComponent, ".uno:ReinstateTrackedChange", {});
+
+    // Then make sure this results in an insert after a delete:
+    // Expected document: a<del>b<b>c
d</b>e</del><ins>b<b>c
d</b>e</ins>f.
+    // First check the content.
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    const OUString& rPara1 = pCursor->GetPointNode().GetTextNode()->GetText();
+    CPPUNIT_ASSERT_EQUAL(u"abc"_ustr, rPara1);
+    pWrtShell->Down(/*bSelect=*/false);
+    const OUString& rPara2 = pCursor->GetPointNode().GetTextNode()->GetText();
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: debc
+    // - Actual  : debc
def
+    // i.e. multi-paragraph delete was inserted with a literal newline 
character instead of creating
+    // a new text node.
+    CPPUNIT_ASSERT_EQUAL(u"debc"_ustr, rPara2);
+    pWrtShell->Down(/*bSelect=*/false);
+    const OUString& rPara3 = pCursor->GetPointNode().GetTextNode()->GetText();
+    CPPUNIT_ASSERT_EQUAL(u"def"_ustr, rPara3);
+    // Check if formatting was copied correctly.
+    pWrtShell->Up(/*bSelect=*/false);
+    pWrtShell->EndPara(/*bSelect=*/false);
+    SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aSet(rView.GetPool());
+    pWrtShell->GetCurAttr(aSet);
+    CPPUNIT_ASSERT(aSet.HasItem(RES_CHRATR_WEIGHT));
+    // Check the redline table: now the insert redline should be 
multi-paragraph.
+    SwDoc* pDoc = pWrtShell->GetDoc();
+    IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess();
+    SwRedlineTable& rRedlines = rIDRA.GetRedlineTable();
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), rRedlines.size());
+    const SwRangeRedline* pRedline1 = rRedlines[0];
+    const SwRedlineData& rRedlineData1 = pRedline1->GetRedlineData(0);
+    CPPUNIT_ASSERT_EQUAL(RedlineType::Delete, rRedlineData1.GetType());
+    const SwRangeRedline* pRedline2 = rRedlines[1];
+    const SwRedlineData& rRedlineData2 = pRedline2->GetRedlineData(0);
+    CPPUNIT_ASSERT_EQUAL(RedlineType::Insert, rRedlineData2.GetType());
+    CPPUNIT_ASSERT_GREATER(pRedline2->Start()->nNode, pRedline2->End()->nNode);
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/sw/source/core/edit/edredln.cxx b/sw/source/core/edit/edredln.cxx
index a28dcff4209c..a99a8f2e459d 100644
--- a/sw/source/core/edit/edredln.cxx
+++ b/sw/source/core/edit/edredln.cxx
@@ -24,6 +24,7 @@
 #include <editsh.hxx>
 #include <frmtool.hxx>
 #include <docsh.hxx>
+#include <swdtflvr.hxx>
 
 RedlineFlags SwEditShell::GetRedlineFlags() const
 {
@@ -89,11 +90,29 @@ void SwEditShell::ReinstatePaM(const SwRangeRedline& 
rRedline, SwPaM& rPaM)
     else if (rRedline.GetType() == RedlineType::Delete)
     {
         // Re-insert after the deletion.
-        OUString aText = rPaM.GetText();
-        ClearMark();
+        SwDocShell* pDocShell = GetDoc()->GetDocShell();
+        if (!pDocShell)
+        {
+            return;
+        }
+
+        SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
+        if (!pWrtShell)
+        {
+            return;
+        }
+
         SwShellCursor* pCursor = getShellCursor(/*bBlock=*/true);
         *pCursor->GetPoint() = *rPaM.End();
-        Insert2(aText);
+        SetMark();
+        *pCursor->GetMark() = *rPaM.Start();
+        rtl::Reference<SwTransferable> pTransfer(new 
SwTransferable(*pWrtShell));
+        // Copy rich text, but don't strip out text inside delete redlines.
+        pTransfer->Copy(/*bIsCut=*/false, /*bDeleteRedlines=*/false);
+        ClearMark();
+        *pCursor->GetPoint() = *rPaM.End();
+        TransferableDataHelper aHelper(pTransfer);
+        SwTransferable::Paste(*pWrtShell, aHelper);
     }
 }
 
diff --git a/sw/source/core/frmedt/fecopy.cxx b/sw/source/core/frmedt/fecopy.cxx
index 6cc0f9d58e4d..61bf3399cd6b 100644
--- a/sw/source/core/frmedt/fecopy.cxx
+++ b/sw/source/core/frmedt/fecopy.cxx
@@ -74,7 +74,7 @@
 using namespace ::com::sun::star;
 
 // Copy for the internal clipboard. Copies all selections to the clipboard.
-void SwFEShell::Copy( SwDoc& rClpDoc, const OUString* pNewClpText )
+void SwFEShell::Copy( SwDoc& rClpDoc, const OUString* pNewClpText, bool 
bDeleteRedlines )
 {
     rClpDoc.GetIDocumentUndoRedo().DoUndo(false); // always false!
 
@@ -118,7 +118,10 @@ void SwFEShell::Copy( SwDoc& rClpDoc, const OUString* 
pNewClpText )
     }
 
     rClpDoc.getIDocumentFieldsAccess().LockExpFields();
-    rClpDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( 
RedlineFlags::DeleteRedlines );
+    if (bDeleteRedlines)
+    {
+        rClpDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( 
RedlineFlags::DeleteRedlines );
+    }
 
     // do we want to copy a FlyFrame?
     if( IsFrameSelected() )
diff --git a/sw/source/uibase/dochdl/swdtflvr.cxx 
b/sw/source/uibase/dochdl/swdtflvr.cxx
index a5b5e191f3e6..d5fa77cf5ab5 100644
--- a/sw/source/uibase/dochdl/swdtflvr.cxx
+++ b/sw/source/uibase/dochdl/swdtflvr.cxx
@@ -403,7 +403,7 @@ namespace
     //source, so that we can WYSIWYG paste. If we want that the destinations
     //styles are used over the source styles, that's a matter of the
     //destination paste code to handle, not the source paste code.
-    void lclOverWriteDoc(SwWrtShell &rSrcWrtShell, SwDoc &rDest)
+    void lclOverWriteDoc(SwWrtShell &rSrcWrtShell, SwDoc &rDest, bool 
bDeleteRedlines = true)
     {
         const SwDoc &rSrc = *rSrcWrtShell.GetDoc();
 
@@ -414,7 +414,7 @@ namespace
         //by the selection, e.g. apply SwDoc::IsUsed on styles ?
         rDest.ReplaceStyles(rSrc, false);
 
-        rSrcWrtShell.Copy(rDest);
+        rSrcWrtShell.Copy(rDest, /*pNewClpText=*/nullptr, bDeleteRedlines);
 
         rDest.GetMetaFieldManager().copyDocumentProperties(rSrc);
     }
@@ -959,7 +959,7 @@ void SwTransferable::PrepareForCopyTextRange(SwPaM & rPaM)
     AddFormat( SotClipboardFormatId::STRING );
 }
 
-int SwTransferable::PrepareForCopy( bool bIsCut )
+int SwTransferable::PrepareForCopy( bool bIsCut, bool bDeleteRedlines )
 {
     int nRet = 1;
     if(!m_pWrtShell)
@@ -1065,7 +1065,7 @@ int SwTransferable::PrepareForCopy( bool bIsCut )
         SwDoc& rTmpDoc = lcl_GetDoc(*m_pClpDocFac);
 
         rTmpDoc.getIDocumentFieldsAccess().LockExpFields();     // Never 
update fields - leave text as is
-        lclOverWriteDoc(*m_pWrtShell, rTmpDoc);
+        lclOverWriteDoc(*m_pWrtShell, rTmpDoc, bDeleteRedlines);
 
         DeleteDDEAndReminderMarks(rTmpDoc);
 
@@ -1197,12 +1197,12 @@ int SwTransferable::PrepareForCopy( bool bIsCut )
     return nRet;
 }
 
-int SwTransferable::Copy( bool bIsCut )
+int SwTransferable::Copy( bool bIsCut, bool bDeleteRedlines )
 {
     if (m_pWrtShell->GetView().GetObjectShell()->isContentExtractionLocked())
         return 0;
 
-    int nRet = PrepareForCopy( bIsCut );
+    int nRet = PrepareForCopy( bIsCut, bDeleteRedlines );
     if ( nRet )
     {
         CopyToClipboard( &m_pWrtShell->GetView().GetEditWin() );
diff --git a/sw/source/uibase/inc/swdtflvr.hxx 
b/sw/source/uibase/inc/swdtflvr.hxx
index c643aa052b0b..e301fa0d7540 100644
--- a/sw/source/uibase/inc/swdtflvr.hxx
+++ b/sw/source/uibase/inc/swdtflvr.hxx
@@ -179,8 +179,8 @@ public:
 
     // copy - methods and helper methods for the copy
     SW_DLLPUBLIC int  Cut();
-    SW_DLLPUBLIC int  Copy( bool bIsCut = false );
-    int  PrepareForCopy( bool bIsCut = false );
+    SW_DLLPUBLIC int  Copy( bool bIsCut = false, bool bDeleteRedlines = true );
+    int  PrepareForCopy( bool bIsCut = false, bool bDeleteRedlines = true );
     void PrepareForCopyTextRange(SwPaM & rPaM);
     void  CalculateAndCopy();                // special for Calculator
     bool  CopyGlossary( SwTextBlocks& rGlossary, const OUString& rStr );

Reply via email to