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 );