Add a comment to clarify what kind of inputs the class handles
[LibreOffice.git] / sw / source / core / doc / docredln.cxx
blob2434d4863fb1d931ef233e64a7ce7d7f58a42433
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <libxml/xmlwriter.h>
21 #include <boost/property_tree/json_parser.hpp>
23 #include <osl/diagnose.h>
24 #include <sal/log.hxx>
25 #include <tools/datetimeutils.hxx>
26 #include <hintids.hxx>
27 #include <svl/itemiter.hxx>
28 #include <editeng/prntitem.hxx>
29 #include <comphelper/lok.hxx>
30 #include <comphelper/string.hxx>
31 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
32 #include <unotools/datetime.hxx>
33 #include <sfx2/viewsh.hxx>
34 #include <o3tl/string_view.hxx>
35 #include <swmodule.hxx>
36 #include <doc.hxx>
37 #include <docredln.hxx>
38 #include <IDocumentUndoRedo.hxx>
39 #include <DocumentContentOperationsManager.hxx>
40 #include <IDocumentRedlineAccess.hxx>
41 #include <IDocumentState.hxx>
42 #include <IDocumentLayoutAccess.hxx>
43 #include <IDocumentStylePoolAccess.hxx>
44 #include <docary.hxx>
45 #include <ndtxt.hxx>
46 #include <redline.hxx>
47 #include <UndoCore.hxx>
48 #include <hints.hxx>
49 #include <pamtyp.hxx>
50 #include <poolfmt.hxx>
51 #include <algorithm>
52 #include <limits>
53 #include <utility>
54 #include <view.hxx>
55 #include <viewopt.hxx>
56 #include <usrpref.hxx>
57 #include <viewsh.hxx>
58 #include <viscrs.hxx>
59 #include <rootfrm.hxx>
60 #include <strings.hrc>
61 #include <swtypes.hxx>
62 #include <wrtsh.hxx>
63 #include <txtfld.hxx>
65 #include <flowfrm.hxx>
66 #include <txtfrm.hxx>
67 #include <annotationmark.hxx>
69 using namespace com::sun::star;
71 #ifdef DBG_UTIL
73 void sw_DebugRedline( const SwDoc* pDoc )
75 static SwRedlineTable::size_type nWatch = 0; // loplugin:constvars:ignore
76 const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable();
77 for( SwRedlineTable::size_type n = 0; n < rTable.size(); ++n )
79 volatile SwRedlineTable::size_type nDummy = 0;
80 const SwRangeRedline* pCurrent = rTable[ n ];
81 const SwRangeRedline* pNext = n+1 < rTable.size() ? rTable[ n+1 ] : nullptr;
82 if( pCurrent == pNext )
83 (void) nDummy;
84 if( n == nWatch )
85 (void) nDummy; // Possible debugger breakpoint
89 #endif
92 SwExtraRedlineTable::~SwExtraRedlineTable()
94 DeleteAndDestroyAll();
97 void SwExtraRedlineTable::dumpAsXml(xmlTextWriterPtr pWriter) const
99 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwExtraRedlineTable"));
100 (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
102 for (sal_uInt16 nCurExtraRedlinePos = 0; nCurExtraRedlinePos < GetSize(); ++nCurExtraRedlinePos)
104 const SwExtraRedline* pExtraRedline = GetRedline(nCurExtraRedlinePos);
105 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwExtraRedline"));
106 (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
107 (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("symbol"), "%s", BAD_CAST(typeid(*pExtraRedline).name()));
108 (void)xmlTextWriterEndElement(pWriter);
110 (void)xmlTextWriterEndElement(pWriter);
113 #if OSL_DEBUG_LEVEL > 0
114 static bool CheckPosition( const SwPosition* pStart, const SwPosition* pEnd )
116 int nError = 0;
117 SwNode* pSttNode = &pStart->GetNode();
118 SwNode* pEndNode = &pEnd->GetNode();
119 SwNode* pSttTab = pSttNode->StartOfSectionNode()->FindTableNode();
120 SwNode* pEndTab = pEndNode->StartOfSectionNode()->FindTableNode();
121 SwNode* pSttStart = pSttNode;
122 while( pSttStart && (!pSttStart->IsStartNode() || pSttStart->IsSectionNode() ||
123 pSttStart->IsTableNode() ) )
124 pSttStart = pSttStart->StartOfSectionNode();
125 SwNode* pEndStart = pEndNode;
126 while( pEndStart && (!pEndStart->IsStartNode() || pEndStart->IsSectionNode() ||
127 pEndStart->IsTableNode() ) )
128 pEndStart = pEndStart->StartOfSectionNode();
129 assert(pSttTab == pEndTab);
130 if( pSttTab != pEndTab )
131 nError = 1;
132 assert(pSttTab || pSttStart == pEndStart);
133 if( !pSttTab && pSttStart != pEndStart )
134 nError |= 2;
135 if( nError )
136 nError += 10;
137 return nError != 0;
139 #endif
141 bool SwExtraRedlineTable::DeleteAllTableRedlines( SwDoc& rDoc, const SwTable& rTable, bool bSaveInUndo, RedlineType nRedlineTypeToDelete )
143 bool bChg = false;
145 if (bSaveInUndo && rDoc.GetIDocumentUndoRedo().DoesUndo())
147 // #TODO - Add 'Undo' support for deleting 'Table Cell' redlines
149 SwUndoRedline* pUndo = new SwUndoRedline( SwUndoId::REDLINE, rRange );
150 if( pUndo->GetRedlSaveCount() )
152 GetIDocumentUndoRedo().AppendUndo(pUndo);
154 else
155 delete pUndo;
159 for (sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < GetSize(); )
161 SwExtraRedline* pExtraRedline = GetRedline(nCurRedlinePos);
162 const SwTableCellRedline* pTableCellRedline = dynamic_cast<const SwTableCellRedline*>(pExtraRedline);
163 if (pTableCellRedline)
165 const SwTableBox *pRedTabBox = &pTableCellRedline->GetTableBox();
166 const SwTable& rRedTable = pRedTabBox->GetSttNd()->FindTableNode()->GetTable();
167 if ( &rRedTable == &rTable )
169 // Redline for this table
170 const SwRedlineData& aRedlineData = pTableCellRedline->GetRedlineData();
171 const RedlineType nRedlineType = aRedlineData.GetType();
173 // Check if this redline object type should be deleted
174 if (RedlineType::Any == nRedlineTypeToDelete || nRedlineTypeToDelete == nRedlineType)
177 DeleteAndDestroy( nCurRedlinePos );
178 bChg = true;
179 continue; // don't increment position after delete
183 ++nCurRedlinePos;
186 if( bChg )
187 rDoc.getIDocumentState().SetModified();
189 return bChg;
192 bool SwExtraRedlineTable::DeleteTableRowRedline( SwDoc* pDoc, const SwTableLine& rTableLine, bool bSaveInUndo, RedlineType nRedlineTypeToDelete )
194 bool bChg = false;
196 if (bSaveInUndo && pDoc->GetIDocumentUndoRedo().DoesUndo())
198 // #TODO - Add 'Undo' support for deleting 'Table Cell' redlines
200 SwUndoRedline* pUndo = new SwUndoRedline( SwUndoId::REDLINE, rRange );
201 if( pUndo->GetRedlSaveCount() )
203 GetIDocumentUndoRedo().AppendUndo(pUndo);
205 else
206 delete pUndo;
210 for(sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < GetSize(); ++nCurRedlinePos )
212 SwExtraRedline* pExtraRedline = GetRedline(nCurRedlinePos);
213 const SwTableRowRedline* pTableRowRedline = dynamic_cast<const SwTableRowRedline*>(pExtraRedline);
214 if (!pTableRowRedline)
215 continue;
216 const SwTableLine& rRedTabLine = pTableRowRedline->GetTableLine();
217 if ( &rRedTabLine == &rTableLine )
219 // Redline for this table row
220 const SwRedlineData& aRedlineData = pTableRowRedline->GetRedlineData();
221 const RedlineType nRedlineType = aRedlineData.GetType();
223 // Check if this redline object type should be deleted
224 if( RedlineType::Any != nRedlineTypeToDelete && nRedlineTypeToDelete != nRedlineType )
225 continue;
227 DeleteAndDestroy( nCurRedlinePos );
228 bChg = true;
232 if( bChg )
233 pDoc->getIDocumentState().SetModified();
235 return bChg;
238 bool SwExtraRedlineTable::DeleteTableCellRedline( SwDoc* pDoc, const SwTableBox& rTableBox, bool bSaveInUndo, RedlineType nRedlineTypeToDelete )
240 bool bChg = false;
242 if (bSaveInUndo && pDoc->GetIDocumentUndoRedo().DoesUndo())
244 // #TODO - Add 'Undo' support for deleting 'Table Cell' redlines
246 SwUndoRedline* pUndo = new SwUndoRedline( SwUndoId::REDLINE, rRange );
247 if( pUndo->GetRedlSaveCount() )
249 GetIDocumentUndoRedo().AppendUndo(pUndo);
251 else
252 delete pUndo;
256 for(sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < GetSize(); ++nCurRedlinePos )
258 SwExtraRedline* pExtraRedline = GetRedline(nCurRedlinePos);
259 const SwTableCellRedline* pTableCellRedline = dynamic_cast<const SwTableCellRedline*>(pExtraRedline);
260 if (!pTableCellRedline)
261 continue;
262 const SwTableBox& rRedTabBox = pTableCellRedline->GetTableBox();
263 if (&rRedTabBox == &rTableBox)
265 // Redline for this table cell
266 const SwRedlineData& aRedlineData = pTableCellRedline->GetRedlineData();
267 const RedlineType nRedlineType = aRedlineData.GetType();
269 // Check if this redline object type should be deleted
270 if( RedlineType::Any != nRedlineTypeToDelete && nRedlineTypeToDelete != nRedlineType )
271 continue;
273 DeleteAndDestroy( nCurRedlinePos );
274 bChg = true;
278 if( bChg )
279 pDoc->getIDocumentState().SetModified();
281 return bChg;
284 namespace
287 void lcl_LOKInvalidateFrames(const sw::BroadcastingModify& rMod, const SwRootFrame* pLayout,
288 SwFrameType const nFrameType, const Point* pPoint)
290 SwIterator<SwFrame, sw::BroadcastingModify, sw::IteratorMode::UnwrapMulti> aIter(rMod);
292 for (SwFrame* pTmpFrame = aIter.First(); pTmpFrame; pTmpFrame = aIter.Next() )
294 if ((pTmpFrame->GetType() & nFrameType) &&
295 (!pLayout || pLayout == pTmpFrame->getRootFrame()) &&
296 (!pTmpFrame->IsFlowFrame() || !SwFlowFrame::CastFlowFrame( pTmpFrame )->IsFollow()))
298 if (pPoint)
300 pTmpFrame->InvalidateSize();
302 // Also empty the text portion cache, so it gets rebuilt, taking the new redlines
303 // into account.
304 if (pTmpFrame->IsTextFrame())
306 auto pTextFrame = static_cast<SwTextFrame*>(pTmpFrame);
307 pTextFrame->ClearPara();
314 void lcl_LOKInvalidateStartEndFrames(SwShellCursor& rCursor)
316 if (!(rCursor.HasMark() &&
317 rCursor.GetPoint()->GetNode().IsContentNode() &&
318 rCursor.GetPoint()->GetNode().GetContentNode()->getLayoutFrame(rCursor.GetShell()->GetLayout()) &&
319 (rCursor.GetMark()->GetNode() == rCursor.GetPoint()->GetNode() ||
320 (rCursor.GetMark()->GetNode().IsContentNode() &&
321 rCursor.GetMark()->GetNode().GetContentNode()->getLayoutFrame(rCursor.GetShell()->GetLayout())))))
323 return;
326 auto [pStartPos, pEndPos] = rCursor.StartEnd(); // SwPosition*
328 lcl_LOKInvalidateFrames(*(pStartPos->GetNode().GetContentNode()),
329 rCursor.GetShell()->GetLayout(),
330 FRM_CNTNT, &rCursor.GetSttPos());
332 lcl_LOKInvalidateFrames(*(pEndPos->GetNode().GetContentNode()),
333 rCursor.GetShell()->GetLayout(),
334 FRM_CNTNT, &rCursor.GetEndPos());
337 bool lcl_LOKRedlineNotificationEnabled()
339 static bool bDisableRedlineComments = getenv("DISABLE_REDLINE") != nullptr;
340 if (comphelper::LibreOfficeKit::isActive() && !bDisableRedlineComments)
341 return true;
343 return false;
346 } // anonymous namespace
348 void SwRedlineTable::setMovedIDIfNeeded(sal_uInt32 nMax)
350 if (nMax > m_nMaxMovedID)
351 m_nMaxMovedID = nMax;
354 /// Emits LOK notification about one addition / removal of a redline item.
355 void SwRedlineTable::LOKRedlineNotification(RedlineNotification nType, SwRangeRedline* pRedline)
357 // Disable since usability is very low beyond some small number of changes.
358 if (!lcl_LOKRedlineNotificationEnabled())
359 return;
361 boost::property_tree::ptree aRedline;
362 aRedline.put("action", (nType == RedlineNotification::Add ? "Add" :
363 (nType == RedlineNotification::Remove ? "Remove" :
364 (nType == RedlineNotification::Modify ? "Modify" : "???"))));
365 aRedline.put("index", pRedline->GetId());
366 aRedline.put("author", pRedline->GetAuthorString(1).toUtf8().getStr());
367 aRedline.put("type", SwRedlineTypeToOUString(pRedline->GetRedlineData().GetType()).toUtf8().getStr());
368 aRedline.put("comment", pRedline->GetRedlineData().GetComment().toUtf8().getStr());
369 aRedline.put("description", pRedline->GetDescr().toUtf8().getStr());
370 OUString sDateTime = utl::toISO8601(pRedline->GetRedlineData().GetTimeStamp().GetUNODateTime());
371 aRedline.put("dateTime", sDateTime.toUtf8().getStr());
373 auto [pStartPos, pEndPos] = pRedline->StartEnd(); // SwPosition*
374 SwContentNode* pContentNd = pRedline->GetPointContentNode();
375 SwView* pView = dynamic_cast<SwView*>(SfxViewShell::Current());
376 if (pView && pContentNd)
378 SwShellCursor aCursor(pView->GetWrtShell(), *pStartPos);
379 aCursor.SetMark();
380 *aCursor.GetMark() = *pEndPos;
382 aCursor.FillRects();
384 SwRects* pRects(&aCursor);
385 std::vector<OString> aRects;
386 for(const SwRect& rNextRect : *pRects)
387 aRects.push_back(rNextRect.SVRect().toString());
389 const OString sRects = comphelper::string::join("; ", aRects);
390 aRedline.put("textRange", sRects.getStr());
392 lcl_LOKInvalidateStartEndFrames(aCursor);
394 // When this notify method is called text invalidation is not done yet
395 // Calling FillRects updates the text area so invalidation will not run on the correct rects
396 // So we need to do an own invalidation here. It invalidates text frames containing the redlining
397 SwDoc& rDoc = pRedline->GetDoc();
398 SwViewShell* pSh;
399 if( !rDoc.IsInDtor() )
401 pSh = rDoc.getIDocumentLayoutAccess().GetCurrentViewShell();
402 if( pSh )
403 for(SwNodeIndex nIdx(pStartPos->GetNode()); nIdx <= pEndPos->GetNode(); ++nIdx)
405 SwContentNode* pContentNode = nIdx.GetNode().GetContentNode();
406 if (pContentNode)
407 pSh->InvalidateWindows(pContentNode->FindLayoutRect());
412 boost::property_tree::ptree aTree;
413 aTree.add_child("redline", aRedline);
414 std::stringstream aStream;
415 boost::property_tree::write_json(aStream, aTree);
416 std::string aPayload = aStream.str();
418 SfxViewShell* pViewShell = SfxViewShell::GetFirst();
419 while (pViewShell)
421 if (pView && pView->GetDocId() == pViewShell->GetDocId())
422 pViewShell->libreOfficeKitViewCallback(nType == RedlineNotification::Modify ? LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED : LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED, OString(aPayload));
423 pViewShell = SfxViewShell::GetNext(*pViewShell);
427 bool SwRedlineTable::Insert(SwRangeRedline*& p)
429 if( p->HasValidRange() )
431 std::pair<vector_type::const_iterator, bool> rv = maVector.insert( p );
432 size_type nP = rv.first - begin();
433 LOKRedlineNotification(RedlineNotification::Add, p);
435 // detect text moving by checking nearby redlines, except during Undo
436 // (apply isMoved() during OpenDocument and DOCX import, too, to fix
437 // missing text moving handling in ODF and e.g. web version of MSO)
438 if ( p->GetDoc().GetIDocumentUndoRedo().DoesUndo() ||
439 p->GetDoc().IsInWriterfilterImport() ||
440 p->GetDoc().IsInXMLImport() )
442 isMoved(nP);
445 p->CallDisplayFunc(nP);
446 if (rv.second)
448 CheckOverlapping(rv.first);
449 if (!mpMaxEndPos || (*(*rv.first)->End()) > *mpMaxEndPos->End())
450 mpMaxEndPos = *rv.first;
452 return rv.second;
454 return InsertWithValidRanges( p );
457 void SwRedlineTable::CheckOverlapping(vector_type::const_iterator it)
459 if (m_bHasOverlappingElements)
460 return;
461 if (maVector.size() <= 1) // a single element cannot be overlapping
462 return;
463 auto pCurr = *it;
464 auto itNext = it + 1;
465 if (itNext != maVector.end())
467 auto pNext = *itNext;
468 if (pCurr->End()->GetNodeIndex() >= pNext->Start()->GetNodeIndex())
470 m_bHasOverlappingElements = true;
471 return;
474 if (it != maVector.begin())
476 auto pPrev = *(it - 1);
477 if (pPrev->End()->GetNodeIndex() >= pCurr->Start()->GetNodeIndex())
478 m_bHasOverlappingElements = true;
482 bool SwRedlineTable::Insert(SwRangeRedline*& p, size_type& rP)
484 if( p->HasValidRange() )
486 std::pair<vector_type::const_iterator, bool> rv = maVector.insert( p );
487 rP = rv.first - begin();
488 p->CallDisplayFunc(rP);
489 if (rv.second)
491 CheckOverlapping(rv.first);
492 if (!mpMaxEndPos || (*(*rv.first)->End()) > *mpMaxEndPos->End())
493 mpMaxEndPos = *rv.first;
495 return rv.second;
497 return InsertWithValidRanges( p, &rP );
500 namespace sw {
502 std::vector<std::unique_ptr<SwRangeRedline>> GetAllValidRanges(std::unique_ptr<SwRangeRedline> p)
504 std::vector<std::unique_ptr<SwRangeRedline>> ret;
505 // Create valid "sub-ranges" from the Selection
506 auto [pStart, pEnd] = p->StartEnd(); // SwPosition*
507 SwPosition aNewStt( *pStart );
508 SwNodes& rNds = aNewStt.GetNodes();
509 SwContentNode* pC;
511 if( !aNewStt.GetNode().IsContentNode() )
513 pC = SwNodes::GoNext(&aNewStt);
514 if( !pC )
515 aNewStt.Assign(rNds.GetEndOfContent());
519 if( aNewStt >= *pEnd )
520 return ret;
522 std::unique_ptr<SwRangeRedline> pNew;
523 do {
524 if( !pNew )
525 pNew.reset(new SwRangeRedline( p->GetRedlineData(), aNewStt ));
526 else
528 pNew->DeleteMark();
529 *pNew->GetPoint() = aNewStt;
532 pNew->SetMark();
533 GoEndSection( pNew->GetPoint() );
534 // i60396: If the redlines starts before a table but the table is the last member
535 // of the section, the GoEndSection will end inside the table.
536 // This will result in an incorrect redline, so we've to go back
537 SwNode* pTab = pNew->GetPoint()->GetNode().StartOfSectionNode()->FindTableNode();
538 // We end in a table when pTab != 0
539 if( pTab && !pNew->GetMark()->GetNode().StartOfSectionNode()->FindTableNode() )
540 { // but our Mark was outside the table => Correction
543 // We want to be before the table
544 pNew->GetPoint()->Assign(*pTab);
545 pC = GoPreviousPos( pNew->GetPoint(), false ); // here we are.
546 if( pC )
547 pNew->GetPoint()->SetContent( 0 );
548 pTab = pNew->GetPoint()->GetNode().StartOfSectionNode()->FindTableNode();
549 } while( pTab ); // If there is another table we have to repeat our step backwards
552 // insert dummy character to the empty table rows to keep their changes
553 SwNode& rBoxNode = pNew->GetMark()->GetNode();
554 if ( rBoxNode.GetDoc().GetIDocumentUndoRedo().DoesUndo() && rBoxNode.GetTableBox() &&
555 rBoxNode.GetTableBox()->GetUpper()->IsEmpty() && rBoxNode.GetTextNode() )
557 ::sw::UndoGuard const undoGuard(rBoxNode.GetDoc().GetIDocumentUndoRedo());
558 rBoxNode.GetTextNode()->InsertDummy();
559 pNew->GetMark()->SetContent( 1 );
562 if( *pNew->GetPoint() > *pEnd )
564 pC = nullptr;
565 if( aNewStt.GetNode() != pEnd->GetNode() )
566 do {
567 SwNode& rCurNd = aNewStt.GetNode();
568 if( rCurNd.IsStartNode() )
570 if( rCurNd.EndOfSectionIndex() < pEnd->GetNodeIndex() )
571 aNewStt.Assign( *rCurNd.EndOfSectionNode() );
572 else
573 break;
575 else if( rCurNd.IsContentNode() )
576 pC = rCurNd.GetContentNode();
577 aNewStt.Adjust(SwNodeOffset(1));
578 } while( aNewStt.GetNodeIndex() < pEnd->GetNodeIndex() );
580 if( aNewStt.GetNode() == pEnd->GetNode() )
581 aNewStt.SetContent(pEnd->GetContentIndex());
582 else if( pC )
584 aNewStt.Assign(*pC, pC->Len() );
587 if( aNewStt <= *pEnd )
588 *pNew->GetPoint() = aNewStt;
590 else
591 aNewStt = *pNew->GetPoint();
592 #if OSL_DEBUG_LEVEL > 0
593 CheckPosition( pNew->GetPoint(), pNew->GetMark() );
594 #endif
596 if( *pNew->GetPoint() != *pNew->GetMark() &&
597 pNew->HasValidRange())
599 ret.push_back(std::move(pNew));
602 if( aNewStt >= *pEnd )
603 break;
604 pC = SwNodes::GoNext(&aNewStt);
605 if( !pC )
606 break;
607 } while( aNewStt < *pEnd );
609 return ret;
612 } // namespace sw
614 static void lcl_setRowNotTracked(SwNode& rNode)
616 SwDoc& rDoc = rNode.GetDoc();
617 const SwTableBox* pTableBox = rNode.GetTableBox();
618 if ( rDoc.GetIDocumentUndoRedo().DoesUndo() && pTableBox )
620 SvxPrintItem aSetTracking(RES_PRINT, false);
621 SwNodeIndex aInsPos( *(pTableBox->GetSttNd()), 1);
622 SwCursor aCursor( SwPosition(aInsPos), nullptr );
623 ::sw::UndoGuard const undoGuard(rNode.GetDoc().GetIDocumentUndoRedo());
624 rDoc.SetRowNotTracked( aCursor, aSetTracking );
628 bool SwRedlineTable::InsertWithValidRanges(SwRangeRedline*& p, size_type* pInsPos)
630 bool bAnyIns = false;
631 bool bInsert = RedlineType::Insert == p->GetType();
632 SwNode* pSttNode = &p->Start()->GetNode();
634 std::vector<std::unique_ptr<SwRangeRedline>> redlines(
635 GetAllValidRanges(std::unique_ptr<SwRangeRedline>(p)));
637 // tdf#147180 set table change tracking in the empty row with text insertion
638 if ( bInsert )
639 lcl_setRowNotTracked(*pSttNode);
641 for (std::unique_ptr<SwRangeRedline> & pRedline : redlines)
643 assert(pRedline->HasValidRange());
644 size_type nInsPos;
645 auto pTmpRedline = pRedline.release();
646 if (Insert(pTmpRedline, nInsPos))
648 // tdf#147180 set table tracking to the table row
649 lcl_setRowNotTracked(pTmpRedline->GetPointNode());
651 pTmpRedline->CallDisplayFunc(nInsPos);
652 bAnyIns = true;
653 if (pInsPos && *pInsPos < nInsPos)
655 *pInsPos = nInsPos;
659 p = nullptr;
660 return bAnyIns;
663 bool CompareSwRedlineTable::operator()(const SwRangeRedline* lhs, const SwRangeRedline* rhs) const
665 return *lhs < *rhs;
668 SwRedlineTable::~SwRedlineTable()
670 maVector.DeleteAndDestroyAll();
673 SwRedlineTable::size_type SwRedlineTable::GetPos(const SwRangeRedline* p) const
675 vector_type::const_iterator it = maVector.find(p);
676 if( it == maVector.end() )
677 return npos;
678 return it - maVector.begin();
681 void SwRedlineTable::Remove( const SwRangeRedline* p )
683 const size_type nPos = GetPos(p);
684 if (nPos == npos)
685 return;
686 Remove(nPos);
689 void SwRedlineTable::Remove( size_type nP )
691 LOKRedlineNotification(RedlineNotification::Remove, maVector[nP]);
692 SwDoc* pDoc = nullptr;
693 if( !nP && 1 == size() )
694 pDoc = &maVector.front()->GetDoc();
696 if (mpMaxEndPos == maVector[nP])
697 mpMaxEndPos = nullptr;
698 maVector.erase( maVector.begin() + nP );
700 if( pDoc && !pDoc->IsInDtor() )
702 SwViewShell* pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell();
703 if( pSh )
704 pSh->InvalidateWindows( SwRect( 0, 0, SAL_MAX_INT32, SAL_MAX_INT32 ) );
708 void SwRedlineTable::DeleteAndDestroyAll()
710 while (!maVector.empty())
712 auto const pRedline = maVector.back();
713 maVector.erase_at(maVector.size() - 1);
714 LOKRedlineNotification(RedlineNotification::Remove, pRedline);
715 delete pRedline;
717 m_bHasOverlappingElements = false;
718 mpMaxEndPos = nullptr;
721 void SwRedlineTable::DeleteAndDestroy(size_type const nP)
723 auto const pRedline = maVector[nP];
724 if (pRedline == mpMaxEndPos)
725 mpMaxEndPos = nullptr;
726 maVector.erase(maVector.begin() + nP);
727 LOKRedlineNotification(RedlineNotification::Remove, pRedline);
728 delete pRedline;
731 SwRedlineTable::size_type SwRedlineTable::FindNextOfSeqNo( size_type nSttPos ) const
733 return nSttPos + 1 < size()
734 ? FindNextSeqNo( operator[]( nSttPos )->GetSeqNo(), nSttPos+1 )
735 : npos;
738 SwRedlineTable::size_type SwRedlineTable::FindPrevOfSeqNo( size_type nSttPos ) const
740 return nSttPos ? FindPrevSeqNo( operator[]( nSttPos )->GetSeqNo(), nSttPos-1 )
741 : npos;
744 /// Find the next or preceding Redline with the same seq.no.
745 /// We can limit the search using look ahead (0 searches the whole array).
746 SwRedlineTable::size_type SwRedlineTable::FindNextSeqNo( sal_uInt16 nSeqNo, size_type nSttPos ) const
748 auto constexpr nLookahead = 20;
749 size_type nRet = npos;
750 if( nSeqNo && nSttPos < size() )
752 size_type nEnd = size();
753 const size_type nTmp = nSttPos + nLookahead;
754 if (nTmp < nEnd)
756 nEnd = nTmp;
759 for( ; nSttPos < nEnd; ++nSttPos )
760 if( nSeqNo == operator[]( nSttPos )->GetSeqNo() )
762 nRet = nSttPos;
763 break;
766 return nRet;
769 SwRedlineTable::size_type SwRedlineTable::FindPrevSeqNo( sal_uInt16 nSeqNo, size_type nSttPos ) const
771 auto constexpr nLookahead = 20;
772 size_type nRet = npos;
773 if( nSeqNo && nSttPos < size() )
775 size_type nEnd = 0;
776 if( nSttPos > nLookahead )
777 nEnd = nSttPos - nLookahead;
779 ++nSttPos;
780 while( nSttPos > nEnd )
782 --nSttPos;
783 if( nSeqNo == operator[](nSttPos)->GetSeqNo() )
785 nRet = nSttPos;
786 break;
790 return nRet;
793 const SwRangeRedline* SwRedlineTable::FindAtPosition( const SwPosition& rSttPos,
794 size_type& rPos,
795 bool bNext ) const
797 const SwRangeRedline* pFnd = nullptr;
798 for( ; rPos < maVector.size() ; ++rPos )
800 const SwRangeRedline* pTmp = (*this)[ rPos ];
801 if( pTmp->HasMark() && pTmp->IsVisible() )
803 auto [pRStt, pREnd] = pTmp->StartEnd(); // SwPosition*
804 if( bNext ? *pRStt <= rSttPos : *pRStt < rSttPos )
806 if( bNext ? *pREnd > rSttPos : *pREnd >= rSttPos )
808 pFnd = pTmp;
809 break;
812 else
813 break;
816 return pFnd;
819 namespace
821 bool lcl_CanCombineWithRange(SwRangeRedline* pOrigin, SwRangeRedline* pActual,
822 SwRangeRedline* pOther, bool bReverseDir, bool bCheckChilds)
824 if (pOrigin->IsVisible() != pOther->IsVisible())
825 return false;
827 if (bReverseDir)
829 if (*(pOther->End()) != *(pActual->Start()))
830 return false;
832 else
834 if (*(pActual->End()) != *(pOther->Start()))
835 return false;
838 if (!pOrigin->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(0)))
840 if (!bCheckChilds || pOther->GetStackCount() <= 1
841 || !pOrigin->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(1)))
842 return false;
844 if (pOther->Start()->GetNode().StartOfSectionNode()
845 != pActual->Start()->GetNode().StartOfSectionNode())
846 return false;
848 return true;
852 const SwPosition& SwRedlineTable::GetMaxEndPos() const
854 assert(!empty() && "cannot call this when the redline table is empty");
855 if (mpMaxEndPos)
856 return *mpMaxEndPos->End();
857 for (const SwRangeRedline* i : maVector)
859 if (!mpMaxEndPos || *i->End() > *mpMaxEndPos->End())
860 mpMaxEndPos = i;
862 assert(mpMaxEndPos);
863 return *mpMaxEndPos->End();
866 void SwRedlineTable::getConnectedArea(size_type nPosOrigin, size_type& rPosStart,
867 size_type& rPosEnd, bool bCheckChilds) const
869 // Keep the original redline .. else we should memorize which children was checked
870 // at the last combined redline.
871 SwRangeRedline* pOrigin = (*this)[nPosOrigin];
872 rPosStart = nPosOrigin;
873 rPosEnd = nPosOrigin;
874 SwRangeRedline* pRedline = pOrigin;
875 SwRangeRedline* pOther;
877 // connection info is already here..only the actual text is missing at import time
878 // so no need to check Redline->GetContentIdx() here yet.
879 while (rPosStart > 0 && (pOther = (*this)[rPosStart - 1])
880 && lcl_CanCombineWithRange(pOrigin, pRedline, pOther, true, bCheckChilds))
882 rPosStart--;
883 pRedline = pOther;
885 pRedline = pOrigin;
886 while (rPosEnd + 1 < size() && (pOther = (*this)[rPosEnd + 1])
887 && lcl_CanCombineWithRange(pOrigin, pRedline, pOther, false, bCheckChilds))
889 rPosEnd++;
890 pRedline = pOther;
894 OUString SwRedlineTable::getTextOfArea(size_type rPosStart, size_type rPosEnd) const
896 // Normally a SwPaM::GetText() would be enough with rPosStart-start and rPosEnd-end
897 // But at import time some text is not present there yet
898 // we have to collect them 1 by 1
900 OUString sRet = u""_ustr;
902 for (size_type nIdx = rPosStart; nIdx <= rPosEnd; ++nIdx)
904 SwRangeRedline* pRedline = (*this)[nIdx];
905 bool bStartWithNonTextNode = false;
907 OUString sNew;
908 if (nullptr == pRedline->GetContentIdx())
910 sNew = pRedline->GetText();
912 else // otherwise it is saved in pContentSect, e.g. during ODT import
914 SwPaM aTmpPaM(pRedline->GetContentIdx()->GetNode(),
915 *pRedline->GetContentIdx()->GetNode().EndOfSectionNode());
916 if (!aTmpPaM.Start()->nNode.GetNode().GetTextNode())
918 bStartWithNonTextNode = true;
920 sNew = aTmpPaM.GetText();
923 if (bStartWithNonTextNode &&
924 sNew[0] == CH_TXTATR_NEWLINE)
926 sRet += sNew.subView(1);
928 else
929 sRet += sNew;
932 return sRet;
935 bool SwRedlineTable::isMoved(size_type rPos) const
937 // If it is already a part of a movement, then don't check it.
938 if ((*this)[rPos]->GetMoved() != 0)
939 return false;
940 // First try with single redline. then try with combined redlines
941 if (isMovedImpl(rPos, false))
942 return true;
943 else
944 return isMovedImpl(rPos, true);
947 bool SwRedlineTable::isMovedImpl(size_type rPos, bool bTryCombined) const
949 bool bRet = false;
950 auto constexpr nLookahead = 20;
951 SwRangeRedline* pRedline = (*this)[ rPos ];
953 // set redline type of the searched pair
954 RedlineType nPairType = pRedline->GetType();
955 if ( RedlineType::Delete == nPairType )
956 nPairType = RedlineType::Insert;
957 else if ( RedlineType::Insert == nPairType )
958 nPairType = RedlineType::Delete;
959 else
960 // only deleted or inserted text can be moved
961 return false;
963 OUString sTrimmed;
964 SwRedlineTable::size_type nPosStart = rPos;
965 SwRedlineTable::size_type nPosEnd = rPos;
967 if (bTryCombined)
969 getConnectedArea(rPos, nPosStart, nPosEnd, false);
970 if (nPosStart != nPosEnd)
971 sTrimmed = getTextOfArea(nPosStart, nPosEnd).trim();
974 if (sTrimmed.isEmpty())
976 // if this redline is visible the content is in this PaM
977 if (nullptr == pRedline->GetContentIdx())
979 sTrimmed = pRedline->GetText().trim();
981 else // otherwise it is saved in pContentSect, e.g. during ODT import
983 SwPaM aTmpPaM(pRedline->GetContentIdx()->GetNode(),
984 *pRedline->GetContentIdx()->GetNode().EndOfSectionNode());
985 sTrimmed = aTmpPaM.GetText().trim();
989 // detection of move needs at least 6 characters with an inner
990 // space after stripping white spaces of the redline to skip
991 // frequent deleted and inserted articles or other common
992 // word parts, e.g. 'the' and 'of a' to detect as text moving
993 if (sTrimmed.getLength() < 6 || sTrimmed.indexOf(' ') == -1)
995 return false;
998 // Todo: lessen the previous condition..:
999 // if the source / destination is a whole node change then maybe space is not needed
1001 // search pair around the actual redline
1002 size_type nEnd = rPos + nLookahead < size()
1003 ? rPos + nLookahead
1004 : size();
1005 size_type nStart = rPos > nLookahead ? rPos - nLookahead : 0;
1006 // first, try to compare to single redlines
1007 // next, try to compare to combined redlines
1008 for (int nPass = 0; nPass < 2 && !bRet; nPass++)
1010 for (size_type nPosAct = nStart; nPosAct < nEnd && !bRet; ++nPosAct)
1012 SwRangeRedline* pPair = (*this)[nPosAct];
1014 // redline must be the requested type and from the same author
1015 if (nPairType != pPair->GetType() || pRedline->GetAuthor() != pPair->GetAuthor())
1017 continue;
1020 OUString sPairTrimmed = u""_ustr;
1021 SwRedlineTable::size_type nPairStart = nPosAct;
1022 SwRedlineTable::size_type nPairEnd = nPosAct;
1024 if (nPass == 0)
1026 // if this redline is visible the content is in this PaM
1027 if (nullptr == pPair->GetContentIdx())
1029 sPairTrimmed = o3tl::trim(pPair->GetText());
1031 else // otherwise it is saved in pContentSect, e.g. during ODT import
1033 // saved in pContentSect, e.g. during ODT import
1034 SwPaM aPairPaM(pPair->GetContentIdx()->GetNode(),
1035 *pPair->GetContentIdx()->GetNode().EndOfSectionNode());
1036 sPairTrimmed = o3tl::trim(aPairPaM.GetText());
1039 else
1041 getConnectedArea(nPosAct, nPairStart, nPairEnd, false);
1042 if (nPairStart != nPairEnd)
1043 sPairTrimmed = getTextOfArea(nPairStart, nPairEnd).trim();
1046 // pair at tracked moving: same text by trimming trailing white spaces
1047 if (abs(sTrimmed.getLength() - sPairTrimmed.getLength()) <= 2
1048 && sTrimmed == sPairTrimmed)
1050 sal_uInt32 nMID = getNewMovedID();
1051 if (nPosStart != nPosEnd)
1053 for (size_type nIdx = nPosStart; nIdx <= nPosEnd; ++nIdx)
1055 (*this)[nIdx]->SetMoved(nMID);
1056 if (nIdx != rPos)
1057 (*this)[nIdx]->InvalidateRange(SwRangeRedline::Invalidation::Add);
1060 else
1061 pRedline->SetMoved(nMID);
1063 //in (nPass == 0) it will only call once .. as nPairStart == nPairEnd == nPosAct
1064 for (size_type nIdx = nPairStart; nIdx <= nPairEnd; ++nIdx)
1066 (*this)[nIdx]->SetMoved(nMID);
1067 (*this)[nIdx]->InvalidateRange(SwRangeRedline::Invalidation::Add);
1070 bRet = true;
1073 //we can skip the combined redlines
1074 if (nPass == 1)
1075 nPosAct = nPairEnd;
1079 return bRet;
1082 void SwRedlineTable::dumpAsXml(xmlTextWriterPtr pWriter) const
1084 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwRedlineTable"));
1085 (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
1087 for (SwRedlineTable::size_type nCurRedlinePos = 0; nCurRedlinePos < size(); ++nCurRedlinePos)
1088 operator[](nCurRedlinePos)->dumpAsXml(pWriter);
1090 (void)xmlTextWriterEndElement(pWriter);
1093 SwRedlineExtraData::~SwRedlineExtraData()
1097 void SwRedlineExtraData::Reject( SwPaM& ) const
1101 bool SwRedlineExtraData::operator == ( const SwRedlineExtraData& ) const
1103 return false;
1106 SwRedlineExtraData_FormatColl::SwRedlineExtraData_FormatColl( OUString aColl,
1107 sal_uInt16 nPoolFormatId,
1108 const SfxItemSet* pItemSet,
1109 bool bFormatAll )
1110 : m_sFormatNm(std::move(aColl)), m_nPoolId(nPoolFormatId), m_bFormatAll(bFormatAll)
1112 if( pItemSet && pItemSet->Count() )
1113 m_pSet.reset( new SfxItemSet( *pItemSet ) );
1116 SwRedlineExtraData_FormatColl::~SwRedlineExtraData_FormatColl()
1120 SwRedlineExtraData* SwRedlineExtraData_FormatColl::CreateNew() const
1122 return new SwRedlineExtraData_FormatColl( m_sFormatNm, m_nPoolId, m_pSet.get(), m_bFormatAll );
1125 void SwRedlineExtraData_FormatColl::Reject( SwPaM& rPam ) const
1127 SwDoc& rDoc = rPam.GetDoc();
1129 // What about Undo? Is it turned off?
1130 SwTextFormatColl* pColl = USHRT_MAX == m_nPoolId
1131 ? rDoc.FindTextFormatCollByName( m_sFormatNm )
1132 : rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( m_nPoolId );
1134 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
1135 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore));
1137 SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() );
1139 const SwPosition* pEnd = rPam.End();
1141 if ( !m_bFormatAll || pEnd->GetContentIndex() == 0 )
1143 // don't reject the format of the next paragraph (that is handled by the next redline)
1144 if (aPam.GetPoint()->GetNode() > aPam.GetMark()->GetNode())
1146 aPam.GetPoint()->Adjust(SwNodeOffset(-1));
1147 SwContentNode* pNode = aPam.GetPoint()->GetNode().GetContentNode();
1148 if ( pNode )
1149 aPam.GetPoint()->SetContent( pNode->Len() );
1150 else
1151 // tdf#147507 set it back to a content node to avoid of crashing
1152 aPam.GetPoint()->Adjust(SwNodeOffset(+1));
1154 else if (aPam.GetPoint()->GetNode() < aPam.GetMark()->GetNode())
1156 aPam.GetMark()->Adjust(SwNodeOffset(-1));
1157 SwContentNode* pNode = aPam.GetMark()->GetNode().GetContentNode();
1158 aPam.GetMark()->SetContent( pNode->Len() );
1162 if( pColl )
1163 rDoc.SetTextFormatColl( aPam, pColl, false );
1165 if( m_pSet )
1166 rDoc.getIDocumentContentOperations().InsertItemSet( aPam, *m_pSet );
1168 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
1171 bool SwRedlineExtraData_FormatColl::operator == ( const SwRedlineExtraData& r) const
1173 const SwRedlineExtraData_FormatColl& rCmp = static_cast<const SwRedlineExtraData_FormatColl&>(r);
1174 return m_sFormatNm == rCmp.m_sFormatNm && m_nPoolId == rCmp.m_nPoolId &&
1175 m_bFormatAll == rCmp.m_bFormatAll &&
1176 ( ( !m_pSet && !rCmp.m_pSet ) ||
1177 ( m_pSet && rCmp.m_pSet && *m_pSet == *rCmp.m_pSet ) );
1180 void SwRedlineExtraData_FormatColl::SetItemSet( const SfxItemSet& rSet )
1182 if( rSet.Count() )
1183 m_pSet.reset( new SfxItemSet( rSet ) );
1184 else
1185 m_pSet.reset();
1188 SwRedlineExtraData_Format::SwRedlineExtraData_Format( const SfxItemSet& rSet )
1190 SfxItemIter aIter( rSet );
1191 for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem())
1193 m_aWhichIds.push_back( pItem->Which() );
1197 SwRedlineExtraData_Format::SwRedlineExtraData_Format(
1198 const SwRedlineExtraData_Format& rCpy )
1199 : SwRedlineExtraData()
1201 m_aWhichIds.insert( m_aWhichIds.begin(), rCpy.m_aWhichIds.begin(), rCpy.m_aWhichIds.end() );
1204 SwRedlineExtraData_Format::~SwRedlineExtraData_Format()
1208 SwRedlineExtraData* SwRedlineExtraData_Format::CreateNew() const
1210 return new SwRedlineExtraData_Format( *this );
1213 void SwRedlineExtraData_Format::Reject( SwPaM& rPam ) const
1215 SwDoc& rDoc = rPam.GetDoc();
1217 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
1218 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore));
1220 // Actually we need to reset the Attribute here!
1221 for( const auto& rWhichId : m_aWhichIds )
1223 rDoc.getIDocumentContentOperations().InsertPoolItem( rPam, *GetDfltAttr( rWhichId ),
1224 SetAttrMode::DONTEXPAND );
1227 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
1230 bool SwRedlineExtraData_Format::operator == ( const SwRedlineExtraData& rCmp ) const
1232 const size_t nEnd = m_aWhichIds.size();
1233 if( nEnd != static_cast<const SwRedlineExtraData_Format&>(rCmp).m_aWhichIds.size() )
1234 return false;
1236 for( size_t n = 0; n < nEnd; ++n )
1238 if( static_cast<const SwRedlineExtraData_Format&>(rCmp).m_aWhichIds[n] != m_aWhichIds[n])
1240 return false;
1243 return true;
1246 SwRedlineData::SwRedlineData( RedlineType eT, std::size_t nAut, sal_uInt32 nMovedID )
1247 : m_pNext( nullptr ), m_pExtraData( nullptr ),
1248 m_aStamp( DateTime::SYSTEM ),
1249 m_nAuthor( nAut ), m_eType( eT ), m_nSeqNo( 0 ), m_bAutoFormat(false), m_nMovedID(nMovedID)
1251 m_aStamp.SetNanoSec( 0 );
1254 SwRedlineData::SwRedlineData(
1255 const SwRedlineData& rCpy,
1256 bool bCpyNext )
1257 : m_pNext( ( bCpyNext && rCpy.m_pNext ) ? new SwRedlineData( *rCpy.m_pNext ) : nullptr )
1258 , m_pExtraData( rCpy.m_pExtraData ? rCpy.m_pExtraData->CreateNew() : nullptr )
1259 , m_sComment( rCpy.m_sComment )
1260 , m_aStamp( rCpy.m_aStamp )
1261 , m_nAuthor( rCpy.m_nAuthor )
1262 , m_eType( rCpy.m_eType )
1263 , m_nSeqNo( rCpy.m_nSeqNo )
1264 , m_bAutoFormat(false)
1265 , m_nMovedID( rCpy.m_nMovedID )
1269 // For sw3io: We now own pNext!
1270 SwRedlineData::SwRedlineData(RedlineType eT, std::size_t nAut, const DateTime& rDT,
1271 sal_uInt32 nMovedID, OUString aCmnt, SwRedlineData *pNxt)
1272 : m_pNext(pNxt), m_pExtraData(nullptr), m_sComment(std::move(aCmnt)), m_aStamp(rDT),
1273 m_nAuthor(nAut), m_eType(eT), m_nSeqNo(0), m_bAutoFormat(false), m_nMovedID(nMovedID)
1277 SwRedlineData::~SwRedlineData()
1279 delete m_pExtraData;
1280 delete m_pNext;
1283 // Check whether the absolute difference between the two dates is no larger than one minute (can
1284 // give inaccurate results if at least one of the dates is not valid/normalized):
1285 static bool deltaOneMinute(DateTime const & t1, DateTime const & t2) {
1286 auto const [min, max] = std::minmax(t1, t2);
1287 // Avoid overflow of `min + tools::Time(0, 1)` below when min is close to the maximum valid
1288 // DateTime:
1289 if (min >= DateTime({31, 12, std::numeric_limits<sal_Int16>::max()}, {23, 59})) {
1290 return true;
1292 return max <= min + tools::Time(0, 1);
1295 bool SwRedlineData::CanCombine(const SwRedlineData& rCmp) const
1297 return m_nAuthor == rCmp.m_nAuthor &&
1298 m_eType == rCmp.m_eType &&
1299 m_sComment == rCmp.m_sComment &&
1300 deltaOneMinute(GetTimeStamp(), rCmp.GetTimeStamp()) &&
1301 m_nMovedID == rCmp.m_nMovedID &&
1302 (( !m_pNext && !rCmp.m_pNext ) ||
1303 ( m_pNext && rCmp.m_pNext &&
1304 m_pNext->CanCombine( *rCmp.m_pNext ))) &&
1305 (( !m_pExtraData && !rCmp.m_pExtraData ) ||
1306 ( m_pExtraData && rCmp.m_pExtraData &&
1307 *m_pExtraData == *rCmp.m_pExtraData ));
1310 // Check if we could/should accept/reject the 2 redlineData at the same time.
1311 // No need to check its children equality
1312 bool SwRedlineData::CanCombineForAcceptReject(const SwRedlineData& rCmp) const
1314 return m_nAuthor == rCmp.m_nAuthor &&
1315 m_eType == rCmp.m_eType &&
1316 m_sComment == rCmp.m_sComment &&
1317 deltaOneMinute(GetTimeStamp(), rCmp.GetTimeStamp()) &&
1318 m_nMovedID == rCmp.m_nMovedID &&
1319 (( !m_pExtraData && !rCmp.m_pExtraData ) ||
1320 ( m_pExtraData && rCmp.m_pExtraData &&
1321 *m_pExtraData == *rCmp.m_pExtraData ));
1324 /// ExtraData is copied. The Pointer's ownership is thus NOT transferred
1325 /// to the Redline Object!
1326 void SwRedlineData::SetExtraData( const SwRedlineExtraData* pData )
1328 delete m_pExtraData;
1330 // Check if there is data - and if so - delete it
1331 if( pData )
1332 m_pExtraData = pData->CreateNew();
1333 else
1334 m_pExtraData = nullptr;
1337 const TranslateId STR_REDLINE_ARY[] =
1339 STR_UNDO_REDLINE_INSERT,
1340 STR_UNDO_REDLINE_DELETE,
1341 STR_UNDO_REDLINE_FORMAT,
1342 STR_UNDO_REDLINE_TABLE,
1343 STR_UNDO_REDLINE_FMTCOLL,
1344 STR_UNDO_REDLINE_PARAGRAPH_FORMAT,
1345 STR_UNDO_REDLINE_TABLE_ROW_INSERT,
1346 STR_UNDO_REDLINE_TABLE_ROW_DELETE,
1347 STR_UNDO_REDLINE_TABLE_CELL_INSERT,
1348 STR_UNDO_REDLINE_TABLE_CELL_DELETE
1351 OUString SwRedlineData::GetDescr() const
1353 return SwResId(STR_REDLINE_ARY[static_cast<int>(GetType())]);
1356 void SwRedlineData::dumpAsXml(xmlTextWriterPtr pWriter) const
1358 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwRedlineData"));
1360 (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
1361 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("id"), BAD_CAST(OString::number(GetSeqNo()).getStr()));
1362 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("author"), BAD_CAST(SwModule::get()->GetRedlineAuthor(GetAuthor()).toUtf8().getStr()));
1363 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("date"), BAD_CAST(DateTimeToOString(GetTimeStamp()).getStr()));
1364 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("descr"), BAD_CAST(GetDescr().toUtf8().getStr()));
1366 OString sRedlineType;
1367 switch (GetType())
1369 case RedlineType::Insert:
1370 sRedlineType = "REDLINE_INSERT"_ostr;
1371 break;
1372 case RedlineType::Delete:
1373 sRedlineType = "REDLINE_DELETE"_ostr;
1374 break;
1375 case RedlineType::Format:
1376 sRedlineType = "REDLINE_FORMAT"_ostr;
1377 break;
1378 default:
1379 sRedlineType = "UNKNOWN"_ostr;
1380 break;
1382 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("type"), BAD_CAST(sRedlineType.getStr()));
1383 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("moved"), BAD_CAST(OString::number(m_nMovedID).getStr()));
1385 (void)xmlTextWriterEndElement(pWriter);
1388 sal_uInt32 SwRangeRedline::s_nLastId = 1;
1390 namespace
1392 void lcl_LOKBroadcastCommentOperation(RedlineType type, const SwPaM& rPam)
1394 if (comphelper::LibreOfficeKit::isActive())
1396 auto eHintType = RedlineType::Delete == type ? SwFormatFieldHintWhich::REDLINED_DELETION: SwFormatFieldHintWhich::INSERTED;
1397 const SwTextNode *pTextNode = rPam.GetPointNode().GetTextNode();
1398 SwTextAttr* pTextAttr = pTextNode ? pTextNode->GetFieldTextAttrAt(rPam.GetPoint()->GetContentIndex() - 1, ::sw::GetTextAttrMode::Default) : nullptr;
1399 SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pTextAttr));
1400 if (pTextField)
1401 const_cast<SwFormatField&>(pTextField->GetFormatField()).Broadcast(SwFormatFieldHint(&pTextField->GetFormatField(), eHintType));
1404 } // anonymous namespace
1406 SwRangeRedline::SwRangeRedline(RedlineType eTyp, const SwPaM& rPam, sal_uInt32 nMovedID )
1407 : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ), m_pRedlineData(
1408 new SwRedlineData(eTyp, GetDoc().getIDocumentRedlineAccess().GetRedlineAuthor(), nMovedID ) )
1410 m_nId( s_nLastId++ )
1412 GetBound().SetOwner(this);
1413 GetBound(false).SetOwner(this);
1415 m_bDelLastPara = false;
1416 m_bIsVisible = true;
1417 if( !rPam.HasMark() )
1418 DeleteMark();
1420 // set default comment for single annotations added or deleted
1421 if ( IsAnnotation() )
1423 SetComment( RedlineType::Delete == eTyp
1424 ? SwResId(STR_REDLINE_COMMENT_DELETED)
1425 : SwResId(STR_REDLINE_COMMENT_ADDED) );
1427 lcl_LOKBroadcastCommentOperation(eTyp, rPam);
1431 SwRangeRedline::SwRangeRedline( const SwRedlineData& rData, const SwPaM& rPam )
1432 : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ),
1433 m_pRedlineData( new SwRedlineData( rData )),
1434 m_nId( s_nLastId++ )
1436 GetBound().SetOwner(this);
1437 GetBound(false).SetOwner(this);
1439 m_bDelLastPara = false;
1440 m_bIsVisible = true;
1441 if( !rPam.HasMark() )
1442 DeleteMark();
1444 // set default comment for single annotations added or deleted
1445 if ( IsAnnotation() )
1447 SetComment( RedlineType::Delete == rData.m_eType
1448 ? SwResId(STR_REDLINE_COMMENT_DELETED)
1449 : SwResId(STR_REDLINE_COMMENT_ADDED) );
1451 lcl_LOKBroadcastCommentOperation(rData.m_eType, rPam);
1455 SwRangeRedline::SwRangeRedline( const SwRedlineData& rData, const SwPosition& rPos )
1456 : SwPaM( rPos ),
1457 m_pRedlineData( new SwRedlineData( rData )),
1458 m_nId( s_nLastId++ )
1460 GetBound().SetOwner(this);
1461 GetBound(false).SetOwner(this);
1463 m_bDelLastPara = false;
1464 m_bIsVisible = true;
1467 SwRangeRedline::SwRangeRedline( const SwRangeRedline& rCpy )
1468 : SwPaM( *rCpy.GetMark(), *rCpy.GetPoint() ),
1469 m_pRedlineData( new SwRedlineData( *rCpy.m_pRedlineData )),
1470 m_nId( s_nLastId++ )
1472 GetBound().SetOwner(this);
1473 GetBound(false).SetOwner(this);
1475 m_bDelLastPara = false;
1476 m_bIsVisible = true;
1477 if( !rCpy.HasMark() )
1478 DeleteMark();
1481 SwRangeRedline::~SwRangeRedline()
1483 if( m_oContentSect )
1485 // delete the ContentSection
1486 if( !GetDoc().IsInDtor() )
1487 GetDoc().getIDocumentContentOperations().DeleteSection( &m_oContentSect->GetNode() );
1488 m_oContentSect.reset();
1490 delete m_pRedlineData;
1493 void MaybeNotifyRedlineModification(SwRangeRedline& rRedline, SwDoc& rDoc)
1495 if (!lcl_LOKRedlineNotificationEnabled())
1496 return;
1498 const SwRedlineTable& rRedTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
1499 for (SwRedlineTable::size_type i = 0; i < rRedTable.size(); ++i)
1501 if (rRedTable[i] == &rRedline)
1503 SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, &rRedline);
1504 break;
1509 void SwRangeRedline::MaybeNotifyRedlinePositionModification(tools::Long nTop)
1511 if (!lcl_LOKRedlineNotificationEnabled())
1512 return;
1514 if(!m_oLOKLastNodeTop || *m_oLOKLastNodeTop != nTop)
1516 m_oLOKLastNodeTop = nTop;
1517 SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, this);
1521 void SwRangeRedline::SetStart( const SwPosition& rPos, SwPosition* pSttPtr )
1523 if( !pSttPtr ) pSttPtr = Start();
1524 *pSttPtr = rPos;
1526 MaybeNotifyRedlineModification(*this, GetDoc());
1529 void SwRangeRedline::SetEnd( const SwPosition& rPos, SwPosition* pEndPtr )
1531 if( !pEndPtr ) pEndPtr = End();
1532 *pEndPtr = rPos;
1534 MaybeNotifyRedlineModification(*this, GetDoc());
1537 /// Do we have a valid Selection?
1538 bool SwRangeRedline::HasValidRange() const
1540 const SwNode* pPtNd = &GetPoint()->GetNode(),
1541 * pMkNd = &GetMark()->GetNode();
1542 if( pPtNd->StartOfSectionNode() == pMkNd->StartOfSectionNode() &&
1543 !pPtNd->StartOfSectionNode()->IsTableNode() &&
1544 // invalid if points on the end of content
1545 // end-of-content only invalid if no content index exists
1546 ( pPtNd != pMkNd || GetContentIdx() != nullptr ||
1547 pPtNd != &pPtNd->GetNodes().GetEndOfContent() )
1549 return true;
1550 return false;
1553 void SwRangeRedline::CallDisplayFunc(size_t nMyPos)
1555 RedlineFlags eShow = RedlineFlags::ShowMask & GetDoc().getIDocumentRedlineAccess().GetRedlineFlags();
1556 if (eShow == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete))
1557 Show(0, nMyPos);
1558 else if (eShow == RedlineFlags::ShowInsert)
1559 Hide(0, nMyPos);
1560 else if (eShow == RedlineFlags::ShowDelete)
1561 ShowOriginal(0, nMyPos);
1564 void SwRangeRedline::Show(sal_uInt16 nLoop, size_t nMyPos, bool bForced)
1566 SwDoc& rDoc = GetDoc();
1568 bool bIsShowChangesInMargin = false;
1569 if ( !bForced )
1571 SwViewShell* pSh = rDoc.getIDocumentLayoutAccess().GetCurrentViewShell();
1572 if (pSh)
1573 bIsShowChangesInMargin = pSh->GetViewOptions()->IsShowChangesInMargin();
1574 else
1575 bIsShowChangesInMargin = SwModule::get()->GetUsrPref(false)->IsShowChangesInMargin();
1578 if( 1 > nLoop && !bIsShowChangesInMargin )
1579 return;
1581 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
1582 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);
1583 ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
1585 switch( GetType() )
1587 case RedlineType::Insert: // Content has been inserted
1588 m_bIsVisible = true;
1589 MoveFromSection(nMyPos);
1590 break;
1592 case RedlineType::Delete: // Content has been deleted
1593 m_bIsVisible = !bIsShowChangesInMargin;
1595 if (m_bIsVisible)
1596 MoveFromSection(nMyPos);
1597 else
1599 switch( nLoop )
1601 case 0: MoveToSection(); break;
1602 case 1: CopyToSection(); break;
1603 case 2: DelCopyOfSection(nMyPos); break;
1606 break;
1608 case RedlineType::Format: // Attributes have been applied
1609 case RedlineType::Table: // Table structure has been modified
1610 InvalidateRange(Invalidation::Add);
1611 break;
1612 default:
1613 break;
1615 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
1618 void SwRangeRedline::Hide(sal_uInt16 nLoop, size_t nMyPos, bool /*bForced*/)
1620 SwDoc& rDoc = GetDoc();
1621 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
1622 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);
1623 ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
1625 switch( GetType() )
1627 case RedlineType::Insert: // Content has been inserted
1628 m_bIsVisible = true;
1629 if( 1 <= nLoop )
1630 MoveFromSection(nMyPos);
1631 break;
1633 case RedlineType::Delete: // Content has been deleted
1634 m_bIsVisible = false;
1635 switch( nLoop )
1637 case 0: MoveToSection(); break;
1638 case 1: CopyToSection(); break;
1639 case 2: DelCopyOfSection(nMyPos); break;
1641 break;
1643 case RedlineType::Format: // Attributes have been applied
1644 case RedlineType::Table: // Table structure has been modified
1645 if( 1 <= nLoop )
1646 InvalidateRange(Invalidation::Remove);
1647 break;
1648 default:
1649 break;
1651 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
1654 void SwRangeRedline::ShowOriginal(sal_uInt16 nLoop, size_t nMyPos, bool /*bForced*/)
1656 SwDoc& rDoc = GetDoc();
1657 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
1658 SwRedlineData* pCur;
1660 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);
1661 ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
1663 // Determine the Type, it's the first on Stack
1664 for( pCur = m_pRedlineData; pCur->m_pNext; )
1665 pCur = pCur->m_pNext;
1667 switch( pCur->m_eType )
1669 case RedlineType::Insert: // Content has been inserted
1670 m_bIsVisible = false;
1671 switch( nLoop )
1673 case 0: MoveToSection(); break;
1674 case 1: CopyToSection(); break;
1675 case 2: DelCopyOfSection(nMyPos); break;
1677 break;
1679 case RedlineType::Delete: // Content has been deleted
1680 m_bIsVisible = true;
1681 if( 1 <= nLoop )
1682 MoveFromSection(nMyPos);
1683 break;
1685 case RedlineType::Format: // Attributes have been applied
1686 case RedlineType::Table: // Table structure has been modified
1687 if( 1 <= nLoop )
1688 InvalidateRange(Invalidation::Remove);
1689 break;
1690 default:
1691 break;
1693 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
1696 // trigger the Layout
1697 void SwRangeRedline::InvalidateRange(Invalidation const eWhy)
1699 auto [pRStt, pREnd] = StartEnd(); // SwPosition*
1700 SwNodeOffset nSttNd = pRStt->GetNodeIndex(),
1701 nEndNd = pREnd->GetNodeIndex();
1702 sal_Int32 nSttCnt = pRStt->GetContentIndex();
1703 sal_Int32 nEndCnt = pREnd->GetContentIndex();
1705 SwNodes& rNds = GetDoc().GetNodes();
1706 for (SwNodeOffset n(nSttNd); n <= nEndNd; ++n)
1708 SwNode* pNode = rNds[n];
1710 if (pNode && pNode->IsTextNode())
1712 SwTextNode* pNd = pNode->GetTextNode();
1714 SwUpdateAttr aHt(
1715 n == nSttNd ? nSttCnt : 0,
1716 n == nEndNd ? nEndCnt : pNd->GetText().getLength(),
1717 RES_UPDATEATTR_FMT_CHG);
1719 pNd->TriggerNodeUpdate(sw::UpdateAttrHint(&aHt, &aHt));
1721 // SwUpdateAttr must be handled first, otherwise indexes are off
1722 if (GetType() == RedlineType::Delete)
1724 sal_Int32 const nStart(n == nSttNd ? nSttCnt : 0);
1725 sal_Int32 const nLen((n == nEndNd ? nEndCnt : pNd->GetText().getLength()) - nStart);
1726 if (eWhy == Invalidation::Add)
1728 sw::RedlineDelText const hint(nStart, nLen);
1729 pNd->CallSwClientNotify(hint);
1731 else
1733 sw::RedlineUnDelText const hint(nStart, nLen);
1734 pNd->CallSwClientNotify(hint);
1737 if (comphelper::LibreOfficeKit::isActive() && IsAnnotation())
1739 auto eHintType = eWhy == Invalidation::Add ? SwFormatFieldHintWhich::INSERTED: SwFormatFieldHintWhich::REMOVED;
1740 const SwTextNode *pTextNode = this->GetPointNode().GetTextNode();
1741 SwTextAttr* pTextAttr = pTextNode ? pTextNode->GetFieldTextAttrAt(this->GetPoint()->GetContentIndex() - 1, ::sw::GetTextAttrMode::Default) : nullptr;
1742 SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pTextAttr));
1743 if (pTextField)
1744 const_cast<SwFormatField&>(pTextField->GetFormatField()).Broadcast(SwFormatFieldHint(&pTextField->GetFormatField(), eHintType));
1751 /** Calculates the start and end position of the intersection rTmp and
1752 text node nNdIdx */
1753 void SwRangeRedline::CalcStartEnd( SwNodeOffset nNdIdx, sal_Int32& rStart, sal_Int32& rEnd ) const
1755 auto [pRStt, pREnd] = StartEnd(); // SwPosition*
1756 if( pRStt->GetNodeIndex() < nNdIdx )
1758 if( pREnd->GetNodeIndex() > nNdIdx )
1760 rStart = 0; // Paragraph is completely enclosed
1761 rEnd = COMPLETE_STRING;
1763 else if (pREnd->GetNodeIndex() == nNdIdx)
1765 rStart = 0; // Paragraph is overlapped in the beginning
1766 rEnd = pREnd->GetContentIndex();
1768 else // redline ends before paragraph
1770 rStart = COMPLETE_STRING;
1771 rEnd = COMPLETE_STRING;
1774 else if( pRStt->GetNodeIndex() == nNdIdx )
1776 rStart = pRStt->GetContentIndex();
1777 if( pREnd->GetNodeIndex() == nNdIdx )
1778 rEnd = pREnd->GetContentIndex(); // Within the Paragraph
1779 else
1780 rEnd = COMPLETE_STRING; // Paragraph is overlapped in the end
1782 else
1784 rStart = COMPLETE_STRING;
1785 rEnd = COMPLETE_STRING;
1789 static void lcl_storeAnnotationMarks(SwDoc& rDoc, const SwPosition* pStart, const SwPosition* pEnd)
1791 // tdf#115815 keep original start position of collapsed annotation ranges
1792 // as temporary bookmarks (removed after file saving and file loading)
1793 IDocumentMarkAccess& rDMA(*rDoc.getIDocumentMarkAccess());
1794 for (auto iter = rDMA.findFirstAnnotationMarkNotStartsBefore(*pStart);
1795 iter != rDMA.getAnnotationMarksEnd(); ++iter)
1797 SwPosition const& rStartPos((**iter).GetMarkStart());
1798 // vector is sorted by start pos, so we can exit early
1799 if ( rStartPos > *pEnd )
1800 break;
1801 if ( *pStart <= rStartPos && rStartPos < *pEnd )
1803 auto pOldMark = rDMA.findAnnotationBookmark((**iter).GetName());
1804 if ( pOldMark == rDMA.getBookmarksEnd() )
1806 // at start of redlines use a 1-character length bookmark range
1807 // instead of a 0-character length bookmark position to avoid its losing
1808 sal_Int32 nLen = (*pStart == rStartPos) ? 1 : 0;
1809 SwPaM aPam( rStartPos.GetNode(), rStartPos.GetContentIndex(),
1810 rStartPos.GetNode(), rStartPos.GetContentIndex() + nLen);
1811 ::sw::mark::Bookmark* pBookmark = rDMA.makeAnnotationBookmark(
1812 aPam,
1813 (**iter).GetName(),
1814 sw::mark::InsertMode::New);
1815 if (pBookmark)
1817 pBookmark->SetKeyCode(vcl::KeyCode());
1818 pBookmark->SetShortName(OUString());
1825 void SwRangeRedline::MoveToSection()
1827 if( !m_oContentSect )
1829 auto [pStart, pEnd] = StartEnd(); // SwPosition*
1831 SwDoc& rDoc = GetDoc();
1832 SwPaM aPam( *pStart, *pEnd );
1833 SwContentNode* pCSttNd = pStart->GetNode().GetContentNode();
1834 SwContentNode* pCEndNd = pEnd->GetNode().GetContentNode();
1836 if( !pCSttNd )
1838 // In order to not move other Redlines' indices, we set them
1839 // to the end (is exclusive)
1840 const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
1841 for(SwRangeRedline* pRedl : rTable)
1843 if( pRedl->GetBound() == *pStart )
1844 pRedl->GetBound() = *pEnd;
1845 if( pRedl->GetBound(false) == *pStart )
1846 pRedl->GetBound(false) = *pEnd;
1850 SwStartNode* pSttNd;
1851 SwNodes& rNds = rDoc.GetNodes();
1852 if( pCSttNd || pCEndNd )
1854 SwTextFormatColl* pColl = (pCSttNd && pCSttNd->IsTextNode() )
1855 ? pCSttNd->GetTextNode()->GetTextColl()
1856 : (pCEndNd && pCEndNd->IsTextNode() )
1857 ? pCEndNd->GetTextNode()->GetTextColl()
1858 : rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD);
1860 pSttNd = rNds.MakeTextSection( rNds.GetEndOfRedlines(),
1861 SwNormalStartNode, pColl );
1862 SwTextNode* pTextNd = rNds[ pSttNd->GetIndex() + 1 ]->GetTextNode();
1864 SwPosition aPos( *pTextNd );
1865 if( pCSttNd && pCEndNd )
1867 // tdf#140982 keep annotation ranges in deletions in margin mode
1868 lcl_storeAnnotationMarks( rDoc, pStart, pEnd );
1869 rDoc.getIDocumentContentOperations().MoveAndJoin( aPam, aPos );
1871 else
1873 if( pCSttNd && !pCEndNd )
1874 m_bDelLastPara = true;
1875 rDoc.getIDocumentContentOperations().MoveRange( aPam, aPos,
1876 SwMoveFlags::DEFAULT );
1879 else
1881 pSttNd = SwNodes::MakeEmptySection( rNds.GetEndOfRedlines() );
1883 SwPosition aPos( *pSttNd->EndOfSectionNode() );
1884 rDoc.getIDocumentContentOperations().MoveRange( aPam, aPos,
1885 SwMoveFlags::DEFAULT );
1887 m_oContentSect.emplace( *pSttNd );
1889 if( pStart == GetPoint() )
1890 Exchange();
1892 DeleteMark();
1894 else
1895 InvalidateRange(Invalidation::Remove);
1898 void SwRangeRedline::CopyToSection()
1900 if( m_oContentSect )
1901 return;
1903 auto [pStart, pEnd] = StartEnd(); // SwPosition*
1905 SwContentNode* pCSttNd = pStart->GetNode().GetContentNode();
1906 SwContentNode* pCEndNd = pEnd->GetNode().GetContentNode();
1908 SwStartNode* pSttNd;
1909 SwDoc& rDoc = GetDoc();
1910 SwNodes& rNds = rDoc.GetNodes();
1912 bool bSaveCopyFlag = rDoc.IsCopyIsMove(),
1913 bSaveRdlMoveFlg = rDoc.getIDocumentRedlineAccess().IsRedlineMove();
1914 rDoc.SetCopyIsMove( true );
1916 // The IsRedlineMove() flag causes the behaviour of the
1917 // DocumentContentOperationsManager::CopyFlyInFlyImpl() method to change,
1918 // which will eventually be called by the CopyRange() below.
1919 rDoc.getIDocumentRedlineAccess().SetRedlineMove(true);
1921 if( pCSttNd )
1923 SwTextFormatColl* pColl = pCSttNd->IsTextNode()
1924 ? pCSttNd->GetTextNode()->GetTextColl()
1925 : rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD);
1927 pSttNd = rNds.MakeTextSection( rNds.GetEndOfRedlines(),
1928 SwNormalStartNode, pColl );
1930 SwPosition aPos( *pSttNd, SwNodeOffset(1) );
1932 // tdf#115815 keep original start position of collapsed annotation ranges
1933 // as temporary bookmarks (removed after file saving and file loading)
1934 lcl_storeAnnotationMarks( rDoc, pStart, pEnd );
1935 rDoc.getIDocumentContentOperations().CopyRange(*this, aPos, SwCopyFlags::CheckPosInFly);
1937 // Take over the style from the EndNode if needed
1938 // We don't want this in Doc::Copy
1939 if( pCEndNd && pCEndNd != pCSttNd )
1941 SwContentNode* pDestNd = aPos.GetNode().GetContentNode();
1942 if( pDestNd )
1944 if( pDestNd->IsTextNode() && pCEndNd->IsTextNode() )
1945 pCEndNd->GetTextNode()->CopyCollFormat(*pDestNd->GetTextNode());
1946 else
1947 pDestNd->ChgFormatColl( pCEndNd->GetFormatColl() );
1951 else
1953 pSttNd = SwNodes::MakeEmptySection( rNds.GetEndOfRedlines() );
1955 if( pCEndNd )
1957 SwPosition aPos( *pSttNd->EndOfSectionNode() );
1958 rDoc.getIDocumentContentOperations().CopyRange(*this, aPos, SwCopyFlags::CheckPosInFly);
1960 else
1962 SwNodeRange aRg( pStart->GetNode(), SwNodeOffset(0), pEnd->GetNode(), SwNodeOffset(1) );
1963 rDoc.GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, *pSttNd->EndOfSectionNode());
1966 m_oContentSect.emplace( *pSttNd );
1968 rDoc.SetCopyIsMove( bSaveCopyFlag );
1969 rDoc.getIDocumentRedlineAccess().SetRedlineMove( bSaveRdlMoveFlg );
1972 void SwRangeRedline::DelCopyOfSection(size_t nMyPos)
1974 if( !m_oContentSect )
1975 return;
1977 auto [pStart, pEnd] = StartEnd(); // SwPosition*
1979 SwDoc& rDoc = GetDoc();
1980 SwPaM aPam( *pStart, *pEnd );
1981 SwContentNode* pCSttNd = pStart->GetNode().GetContentNode();
1982 SwContentNode* pCEndNd = pEnd->GetNode().GetContentNode();
1984 if( !pCSttNd )
1986 // In order to not move other Redlines' indices, we set them
1987 // to the end (is exclusive)
1988 const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
1989 for(SwRangeRedline* pRedl : rTable)
1991 if( pRedl->GetBound() == *pStart )
1992 pRedl->GetBound() = *pEnd;
1993 if( pRedl->GetBound(false) == *pStart )
1994 pRedl->GetBound(false) = *pEnd;
1998 if( pCSttNd && pCEndNd )
2000 // #i100466# - force a <join next> on <delete and join> operation
2001 // tdf#125319 - rather not?
2002 rDoc.getIDocumentContentOperations().DeleteAndJoin(aPam/*, true*/);
2004 else if( pCSttNd || pCEndNd )
2006 if( pCSttNd && !pCEndNd )
2007 m_bDelLastPara = true;
2008 rDoc.getIDocumentContentOperations().DeleteRange( aPam );
2010 if( m_bDelLastPara )
2012 // To prevent dangling references to the paragraph to
2013 // be deleted, redline that point into this paragraph should be
2014 // moved to the new end position. Since redlines in the redline
2015 // table are sorted and the pEnd position is an endnode (see
2016 // bDelLastPara condition above), only redlines before the
2017 // current ones can be affected.
2018 const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
2019 size_t n = nMyPos;
2020 for( bool bBreak = false; !bBreak && n > 0; )
2022 --n;
2023 bBreak = true;
2024 if( rTable[ n ]->GetBound() == *aPam.GetPoint() )
2026 rTable[ n ]->GetBound() = *pEnd;
2027 bBreak = false;
2029 if( rTable[ n ]->GetBound(false) == *aPam.GetPoint() )
2031 rTable[ n ]->GetBound(false) = *pEnd;
2032 bBreak = false;
2036 *GetPoint() = *pEnd;
2037 *GetMark() = *pEnd;
2038 DeleteMark();
2040 aPam.DeleteMark();
2041 aPam.GetPoint()->SetContent(0);;
2042 rDoc.getIDocumentContentOperations().DelFullPara( aPam );
2045 else
2047 rDoc.getIDocumentContentOperations().DeleteRange( aPam );
2050 if( pStart == GetPoint() )
2051 Exchange();
2053 DeleteMark();
2056 void SwRangeRedline::MoveFromSection(size_t nMyPos)
2058 if( m_oContentSect )
2060 SwDoc& rDoc = GetDoc();
2061 const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
2062 std::vector<SwPosition*> aBeforeArr, aBehindArr;
2063 bool bBreak = false;
2064 SwRedlineTable::size_type n;
2066 for( n = nMyPos+1; !bBreak && n < rTable.size(); ++n )
2068 bBreak = true;
2069 if( rTable[ n ]->GetBound() == *GetPoint() )
2071 SwRangeRedline* pRedl = rTable[n];
2072 aBehindArr.push_back(&pRedl->GetBound());
2073 bBreak = false;
2075 if( rTable[ n ]->GetBound(false) == *GetPoint() )
2077 SwRangeRedline* pRedl = rTable[n];
2078 aBehindArr.push_back(&pRedl->GetBound(false));
2079 bBreak = false;
2082 for( bBreak = false, n = nMyPos; !bBreak && n ; )
2084 --n;
2085 bBreak = true;
2086 if( rTable[ n ]->GetBound() == *GetPoint() )
2088 SwRangeRedline* pRedl = rTable[n];
2089 aBeforeArr.push_back(&pRedl->GetBound());
2090 bBreak = false;
2092 if( rTable[ n ]->GetBound(false) == *GetPoint() )
2094 SwRangeRedline* pRedl = rTable[n];
2095 aBeforeArr.push_back(&pRedl->GetBound(false));
2096 bBreak = false;
2100 const SwNode* pKeptContentSectNode( &m_oContentSect->GetNode() ); // #i95711#
2102 SwPaM aPam( m_oContentSect->GetNode(),
2103 *m_oContentSect->GetNode().EndOfSectionNode(), SwNodeOffset(1),
2104 SwNodeOffset( m_bDelLastPara ? -2 : -1 ) );
2105 SwContentNode* pCNd = aPam.GetPointContentNode();
2106 if( pCNd )
2107 aPam.GetPoint()->SetContent( pCNd->Len() );
2108 else
2109 aPam.GetPoint()->Adjust(SwNodeOffset(+1));
2111 SwFormatColl* pColl = pCNd && pCNd->Len() && aPam.GetPoint()->GetNode() !=
2112 aPam.GetMark()->GetNode()
2113 ? pCNd->GetFormatColl() : nullptr;
2115 SwNodeIndex aNdIdx( GetPoint()->GetNode(), -1 );
2116 const sal_Int32 nPos = GetPoint()->GetContentIndex();
2118 SwPosition aPos( *GetPoint() );
2119 if( m_bDelLastPara && *aPam.GetPoint() == *aPam.GetMark() )
2121 aPos.Adjust(SwNodeOffset(-1));
2123 rDoc.getIDocumentContentOperations().AppendTextNode( aPos );
2125 else
2127 rDoc.getIDocumentContentOperations().MoveRange( aPam, aPos,
2128 SwMoveFlags::ALLFLYS );
2131 SetMark();
2132 *GetPoint() = std::move(aPos);
2133 GetMark()->Assign(aNdIdx.GetIndex() + 1);
2134 pCNd = GetMark()->GetNode().GetContentNode();
2135 if( pCNd )
2136 GetMark()->SetContent( nPos );
2138 if( m_bDelLastPara )
2140 GetPoint()->Adjust(SwNodeOffset(+1));
2141 pCNd = GetPointContentNode();
2142 m_bDelLastPara = false;
2144 else if( pColl )
2145 pCNd = GetPointContentNode();
2147 if( pColl && pCNd )
2148 pCNd->ChgFormatColl( pColl );
2151 // #i95771#
2152 // Under certain conditions the previous <SwDoc::Move(..)> has already
2153 // removed the change tracking section of this <SwRangeRedline> instance from
2154 // the change tracking nodes area.
2155 // Thus, check if <pContentSect> still points to the change tracking section
2156 // by comparing it with the "indexed" <SwNode> instance copied before
2157 // perform the intrinsic move.
2158 // Note: Such condition is e.g. a "delete" change tracking only containing a table.
2159 if ( &m_oContentSect->GetNode() == pKeptContentSectNode )
2161 rDoc.getIDocumentContentOperations().DeleteSection( &m_oContentSect->GetNode() );
2163 m_oContentSect.reset();
2165 // adjustment of redline table positions must take start and
2166 // end into account, not point and mark.
2167 for( auto& pItem : aBeforeArr )
2168 *pItem = *Start();
2169 for( auto& pItem : aBehindArr )
2170 *pItem = *End();
2172 else
2173 InvalidateRange(Invalidation::Add);
2176 // for Undo
2177 void SwRangeRedline::SetContentIdx( const SwNodeIndex& rIdx )
2179 if( !m_oContentSect )
2181 m_oContentSect = rIdx;
2182 m_bIsVisible = false;
2184 else
2186 OSL_FAIL("SwRangeRedline::SetContentIdx: invalid state");
2190 // for Undo
2191 void SwRangeRedline::ClearContentIdx()
2193 if( m_oContentSect )
2195 m_oContentSect.reset();
2197 else
2199 OSL_FAIL("SwRangeRedline::ClearContentIdx: invalid state");
2203 bool SwRangeRedline::CanCombine( const SwRangeRedline& rRedl ) const
2205 return IsVisible() && rRedl.IsVisible() &&
2206 m_pRedlineData->CanCombine( *rRedl.m_pRedlineData );
2209 void SwRangeRedline::PushData( const SwRangeRedline& rRedl, bool bOwnAsNext )
2211 SwRedlineData* pNew = new SwRedlineData( *rRedl.m_pRedlineData, false );
2212 if( bOwnAsNext )
2214 pNew->m_pNext = m_pRedlineData;
2215 m_pRedlineData = pNew;
2217 else
2219 pNew->m_pNext = m_pRedlineData->m_pNext;
2220 m_pRedlineData->m_pNext = pNew;
2224 bool SwRangeRedline::PopData()
2226 if( !m_pRedlineData->m_pNext )
2227 return false;
2228 SwRedlineData* pCur = m_pRedlineData;
2229 m_pRedlineData = pCur->m_pNext;
2230 pCur->m_pNext = nullptr;
2231 delete pCur;
2232 return true;
2235 bool SwRangeRedline::PopAllDataAfter(int depth)
2237 assert(depth > 0);
2238 SwRedlineData* pCur = m_pRedlineData;
2239 while (depth > 1)
2241 pCur = pCur->m_pNext;
2242 if (!pCur)
2243 return false;
2244 depth--;
2247 while (pCur->m_pNext)
2249 SwRedlineData* pToDelete = pCur->m_pNext;
2250 pCur->m_pNext = pToDelete->m_pNext;
2251 delete pToDelete;
2253 return true;
2256 sal_uInt16 SwRangeRedline::GetStackCount() const
2258 sal_uInt16 nRet = 1;
2259 for( SwRedlineData* pCur = m_pRedlineData; pCur->m_pNext; pCur = pCur->m_pNext )
2260 ++nRet;
2261 return nRet;
2264 std::size_t SwRangeRedline::GetAuthor( sal_uInt16 nPos ) const
2266 return GetRedlineData(nPos).m_nAuthor;
2269 OUString const & SwRangeRedline::GetAuthorString( sal_uInt16 nPos ) const
2271 return SwModule::get()->GetRedlineAuthor(GetRedlineData(nPos).m_nAuthor);
2274 sal_uInt32 SwRangeRedline::GetMovedID(sal_uInt16 nPos) const
2276 return GetRedlineData(nPos).m_nMovedID;
2279 const DateTime& SwRangeRedline::GetTimeStamp(sal_uInt16 nPos) const
2281 return GetRedlineData(nPos).m_aStamp;
2284 RedlineType SwRangeRedline::GetType( sal_uInt16 nPos ) const
2286 return GetRedlineData(nPos).m_eType;
2289 bool SwRangeRedline::IsAnnotation() const
2291 return GetText().getLength() == 1 && GetText()[0] == CH_TXTATR_INWORD;
2294 const OUString& SwRangeRedline::GetComment( sal_uInt16 nPos ) const
2296 return GetRedlineData(nPos).m_sComment;
2299 bool SwRangeRedline::operator<( const SwRangeRedline& rCmp ) const
2301 auto [pStart, pEnd] = StartEnd();
2302 auto [pCmpStart, pCmpEnd] = rCmp.StartEnd();
2303 if (*pStart < *pCmpStart)
2304 return true;
2306 return *pStart == *pCmpStart && *pEnd < *pCmpEnd;
2309 const SwRedlineData & SwRangeRedline::GetRedlineData(const sal_uInt16 nPos) const
2311 SwRedlineData * pCur = m_pRedlineData;
2313 sal_uInt16 nP = nPos;
2315 while (nP > 0 && nullptr != pCur->m_pNext)
2317 pCur = pCur->m_pNext;
2319 nP--;
2322 SAL_WARN_IF( nP != 0, "sw.core", "Pos " << nPos << " is " << nP << " too big");
2324 return *pCur;
2327 OUString SwRangeRedline::GetDescr(bool bSimplified)
2329 // get description of redline data (e.g.: "insert $1")
2330 OUString aResult = GetRedlineData().GetDescr();
2332 SwPaM * pPaM = nullptr;
2333 bool bDeletePaM = false;
2335 // if this redline is visible the content is in this PaM
2336 if (!m_oContentSect.has_value())
2338 pPaM = this;
2340 else // otherwise it is saved in pContentSect
2342 pPaM = new SwPaM( m_oContentSect->GetNode(), *m_oContentSect->GetNode().EndOfSectionNode() );
2343 bDeletePaM = true;
2346 OUString sDescr = DenoteSpecialCharacters(pPaM->GetText().replace('\n', ' '), /*bQuoted=*/!bSimplified);
2347 if (const SwTextNode *pTextNode = pPaM->GetPointNode().GetTextNode())
2349 if (const SwTextAttr* pTextAttr = pTextNode->GetFieldTextAttrAt(pPaM->GetPoint()->GetContentIndex() - 1, ::sw::GetTextAttrMode::Default))
2351 sDescr = ( bSimplified ? u""_ustr : SwResId(STR_START_QUOTE) )
2352 + pTextAttr->GetFormatField().GetField()->GetFieldName()
2353 + ( bSimplified ? u""_ustr : SwResId(STR_END_QUOTE) );
2357 // replace $1 in description by description of the redlines text
2358 const OUString aTmpStr = ShortenString(sDescr, nUndoStringLength, SwResId(STR_LDOTS));
2360 if (!bSimplified)
2362 SwRewriter aRewriter;
2363 aRewriter.AddRule(UndoArg1, aTmpStr);
2365 aResult = aRewriter.Apply(aResult);
2367 else
2369 aResult = aTmpStr;
2370 // more shortening
2371 sal_Int32 nPos = aTmpStr.indexOf(SwResId(STR_LDOTS));
2372 if (nPos > 5)
2373 aResult = aTmpStr.copy(0, nPos + SwResId(STR_LDOTS).getLength());
2376 if (bDeletePaM)
2377 delete pPaM;
2379 return aResult;
2382 void SwRangeRedline::dumpAsXml(xmlTextWriterPtr pWriter) const
2384 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwRangeRedline"));
2386 (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
2388 const SwRedlineData* pRedlineData = m_pRedlineData;
2389 while (pRedlineData)
2391 pRedlineData->dumpAsXml(pWriter);
2392 pRedlineData = pRedlineData->Next();
2395 SwPaM::dumpAsXml(pWriter);
2397 (void)xmlTextWriterEndElement(pWriter);
2400 void SwExtraRedlineTable::Insert( SwExtraRedline* p )
2402 m_aExtraRedlines.push_back( p );
2403 //p->CallDisplayFunc();
2406 void SwExtraRedlineTable::DeleteAndDestroy(sal_uInt16 const nPos)
2409 SwDoc* pDoc = 0;
2410 if( !nP && nL && nL == size() )
2411 pDoc = front()->GetDoc();
2414 delete m_aExtraRedlines[nPos];
2415 m_aExtraRedlines.erase(m_aExtraRedlines.begin() + nPos);
2418 SwViewShell* pSh;
2419 if( pDoc && !pDoc->IsInDtor() &&
2420 0 != ( pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ) )
2421 pSh->InvalidateWindows( SwRect( 0, 0, SAL_MAX_INT32, SAL_MAX_INT32 ) );
2425 void SwExtraRedlineTable::DeleteAndDestroyAll()
2427 while (!m_aExtraRedlines.empty())
2429 auto const pRedline = m_aExtraRedlines.back();
2430 m_aExtraRedlines.pop_back();
2431 delete pRedline;
2435 SwExtraRedline::~SwExtraRedline()
2439 SwTableRowRedline::SwTableRowRedline(const SwRedlineData& rData, const SwTableLine& rTableLine)
2440 : m_aRedlineData(rData)
2441 , m_rTableLine(rTableLine)
2445 SwTableRowRedline::~SwTableRowRedline()
2449 SwTableCellRedline::SwTableCellRedline(const SwRedlineData& rData, const SwTableBox& rTableBox)
2450 : m_aRedlineData(rData)
2451 , m_rTableBox(rTableBox)
2455 SwTableCellRedline::~SwTableCellRedline()
2459 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */