Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/sw/source/core/doc/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 82 kB image not shown  

Quelle  docredln.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */


#include <libxml/xmlwriter.h>
#include <boost/property_tree/json_parser.hpp>

#include <osl/diagnose.h>
#include <sal/log.hxx>
#include <tools/datetimeutils.hxx>
#include <hintids.hxx>
#include <svl/itemiter.hxx>
#include <editeng/prntitem.hxx>
#include <comphelper/lok.hxx>
#include <comphelper/string.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <unotools/datetime.hxx>
#include <sfx2/viewsh.hxx>
#include <o3tl/string_view.hxx>
#include <swmodule.hxx>
#include <doc.hxx>
#include <docredln.hxx>
#include <IDocumentUndoRedo.hxx>
#include <DocumentContentOperationsManager.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <IDocumentState.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentStylePoolAccess.hxx>
#include <docary.hxx>
#include <ndtxt.hxx>
#include <redline.hxx>
#include <UndoCore.hxx>
#include <hints.hxx>
#include <pamtyp.hxx>
#include <poolfmt.hxx>
#include <algorithm>
#include <limits>
#include <utility>
#include <view.hxx>
#include <viewopt.hxx>
#include <usrpref.hxx>
#include <viewsh.hxx>
#include <viscrs.hxx>
#include <rootfrm.hxx>
#include <strings.hrc>
#include <swtypes.hxx>
#include <wrtsh.hxx>
#include <txtfld.hxx>

#include <flowfrm.hxx>
#include <txtfrm.hxx>
#include <annotationmark.hxx>

using namespace com::sun::star;

#ifdef DBG_UTIL

    void sw_DebugRedline( const SwDoc& rDoc )
    {
        static SwRedlineTable::size_type nWatch = 0; // loplugin:constvars:ignore
        const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
        for( SwRedlineTable::size_type n = 0; n < rTable.size(); ++n )
        {
            volatile SwRedlineTable::size_type nDummy = 0;
            const SwRangeRedline* pCurrent = rTable[ n ];
            const SwRangeRedline* pNext = n+1 < rTable.size() ? rTable[ n+1 ] : nullptr;
            if( pCurrent == pNext )
                (void) nDummy;
            if( n == nWatch )
                (void) nDummy; // Possible debugger breakpoint
        }
    }

#endif


SwExtraRedlineTable::~SwExtraRedlineTable()
{
    DeleteAndDestroyAll();
}

void SwExtraRedlineTable::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwExtraRedlineTable"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p"this);

    for (sal_uInt16 nCurExtraRedlinePos = 0; nCurExtraRedlinePos < GetSize(); ++nCurExtraRedlinePos)
    {
        const SwExtraRedline* pExtraRedline = GetRedline(nCurExtraRedlinePos);
        (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwExtraRedline"));
        (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p"this);
        (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("symbol"), "%s", BAD_CAST(typeid(*pExtraRedline).name()));
        (void)xmlTextWriterEndElement(pWriter);
    }
    (void)xmlTextWriterEndElement(pWriter);
}

#if OSL_DEBUG_LEVEL > 0
static bool CheckPosition( const SwPosition* pStart, const SwPosition* pEnd )
{
    int nError = 0;
    SwNode* pSttNode = &pStart->GetNode();
    SwNode* pEndNode = &pEnd->GetNode();
    SwNode* pSttTab = pSttNode->StartOfSectionNode()->FindTableNode();
    SwNode* pEndTab = pEndNode->StartOfSectionNode()->FindTableNode();
    SwNode* pSttStart = pSttNode;
    while( pSttStart && (!pSttStart->IsStartNode() || pSttStart->IsSectionNode() ||
        pSttStart->IsTableNode() ) )
        pSttStart = pSttStart->StartOfSectionNode();
    SwNode* pEndStart = pEndNode;
    while( pEndStart && (!pEndStart->IsStartNode() || pEndStart->IsSectionNode() ||
        pEndStart->IsTableNode() ) )
        pEndStart = pEndStart->StartOfSectionNode();
    assert(pSttTab == pEndTab);
    if( pSttTab != pEndTab )
        nError = 1;
    assert(pSttTab || pSttStart == pEndStart);
    if( !pSttTab && pSttStart != pEndStart )
        nError |= 2;
    if( nError )
        nError += 10;
    return nError != 0;
}
#endif

bool SwExtraRedlineTable::DeleteAllTableRedlines( SwDoc& rDoc, const SwTable& rTable, bool bSaveInUndo, RedlineType nRedlineTypeToDelete )
{
    bool bChg = false;

    if (bSaveInUndo && rDoc.GetIDocumentUndoRedo().DoesUndo())
    {
        // #TODO - Add 'Undo' support for deleting 'Table Cell' redlines
        /*
        SwUndoRedline* pUndo = new SwUndoRedline( SwUndoId::REDLINE, rRange );
        if( pUndo->GetRedlSaveCount() )
        {
            GetIDocumentUndoRedo().AppendUndo(pUndo);
        }
        else
            delete pUndo;
        */

    }

    for (sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < GetSize(); )
    {
        SwExtraRedline* pExtraRedline = GetRedline(nCurRedlinePos);
        const SwTableCellRedline* pTableCellRedline = dynamic_cast<const SwTableCellRedline*>(pExtraRedline);
        if (pTableCellRedline)
        {
            const SwTableBox *pRedTabBox = &pTableCellRedline->GetTableBox();
            const SwTable& rRedTable = pRedTabBox->GetSttNd()->FindTableNode()->GetTable();
            if ( &rRedTable == &rTable )
            {
                // Redline for this table
                const SwRedlineData& aRedlineData = pTableCellRedline->GetRedlineData();
                const RedlineType nRedlineType = aRedlineData.GetType();

                // Check if this redline object type should be deleted
                if (RedlineType::Any == nRedlineTypeToDelete || nRedlineTypeToDelete == nRedlineType)
                {

                    DeleteAndDestroy( nCurRedlinePos );
                    bChg = true;
                    continue// don't increment position after delete
                }
            }
        }
        ++nCurRedlinePos;
    }

    if( bChg )
        rDoc.getIDocumentState().SetModified();

    return bChg;
}

bool SwExtraRedlineTable::DeleteTableRowRedline( SwDoc& rDoc, const SwTableLine&&nbsp;rTableLine, bool bSaveInUndo, RedlineType nRedlineTypeToDelete )
{
    bool bChg = false;

    if (bSaveInUndo && rDoc.GetIDocumentUndoRedo().DoesUndo())
    {
        // #TODO - Add 'Undo' support for deleting 'Table Cell' redlines
        /*
        SwUndoRedline* pUndo = new SwUndoRedline( SwUndoId::REDLINE, rRange );
        if( pUndo->GetRedlSaveCount() )
        {
            GetIDocumentUndoRedo().AppendUndo(pUndo);
        }
        else
            delete pUndo;
        */

    }

    for(sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < GetSize(); ++nCurRedlinePos )
    {
        SwExtraRedline* pExtraRedline = GetRedline(nCurRedlinePos);
        const SwTableRowRedline* pTableRowRedline = dynamic_cast<const SwTableRowRedline*>(pExtraRedline);
        if (!pTableRowRedline)
            continue;
        const SwTableLine& rRedTabLine = pTableRowRedline->GetTableLine();
        if ( &rRedTabLine == &rTableLine )
        {
            // Redline for this table row
            const SwRedlineData& aRedlineData = pTableRowRedline->GetRedlineData();
            const RedlineType nRedlineType = aRedlineData.GetType();

            // Check if this redline object type should be deleted
            if( RedlineType::Any != nRedlineTypeToDelete && nRedlineTypeToDelete != nRedlineType )
                continue;

            DeleteAndDestroy( nCurRedlinePos );
            bChg = true;
        }
    }

    if( bChg )
        rDoc.getIDocumentState().SetModified();

    return bChg;
}

bool SwExtraRedlineTable::DeleteTableCellRedline( SwDoc& rDoc, const SwTableBox&&nbsp;rTableBox, bool bSaveInUndo, RedlineType nRedlineTypeToDelete )
{
    bool bChg = false;

    if (bSaveInUndo && rDoc.GetIDocumentUndoRedo().DoesUndo())
    {
        // #TODO - Add 'Undo' support for deleting 'Table Cell' redlines
        /*
        SwUndoRedline* pUndo = new SwUndoRedline( SwUndoId::REDLINE, rRange );
        if( pUndo->GetRedlSaveCount() )
        {
            GetIDocumentUndoRedo().AppendUndo(pUndo);
        }
        else
            delete pUndo;
        */

    }

    for(sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < GetSize(); ++nCurRedlinePos )
    {
        SwExtraRedline* pExtraRedline = GetRedline(nCurRedlinePos);
        const SwTableCellRedline* pTableCellRedline = dynamic_cast<const SwTableCellRedline*>(pExtraRedline);
        if (!pTableCellRedline)
            continue;
        const SwTableBox& rRedTabBox = pTableCellRedline->GetTableBox();
        if (&rRedTabBox == &rTableBox)
        {
            // Redline for this table cell
            const SwRedlineData& aRedlineData = pTableCellRedline->GetRedlineData();
            const RedlineType nRedlineType = aRedlineData.GetType();

            // Check if this redline object type should be deleted
            if( RedlineType::Any != nRedlineTypeToDelete && nRedlineTypeToDelete != nRedlineType )
                continue;

            DeleteAndDestroy( nCurRedlinePos );
            bChg = true;
        }
    }

    if( bChg )
        rDoc.getIDocumentState().SetModified();

    return bChg;
}

namespace
{

void lcl_LOKInvalidateFrames(const sw::BroadcastingModify& rMod, const SwRootFrame* pLayout,
        SwFrameType const nFrameType, const Point* pPoint)
{
    SwIterator<SwFrame, sw::BroadcastingModify, sw::IteratorMode::UnwrapMulti> aIter(rMod);

    for (SwFrame* pTmpFrame = aIter.First(); pTmpFrame; pTmpFrame = aIter.Next() )
    {
        if ((pTmpFrame->GetType() & nFrameType) &&
            (!pLayout || pLayout == pTmpFrame->getRootFrame()) &&
            (!pTmpFrame->IsFlowFrame() || !SwFlowFrame::CastFlowFrame( pTmpFrame )->IsFollow()))
        {
            if (pPoint)
            {
                pTmpFrame->InvalidateSize();

                // Also empty the text portion cache, so it gets rebuilt, taking the new redlines
                // into account.
                if (pTmpFrame->IsTextFrame())
                {
                    auto pTextFrame = static_cast<SwTextFrame*>(pTmpFrame);
                    pTextFrame->ClearPara();
                }
            }
        }
    }
}

void lcl_LOKInvalidateStartEndFrames(SwShellCursor& rCursor)
{
    if (!(rCursor.HasMark() &&
        rCursor.GetPoint()->GetNode().IsContentNode() &&
        rCursor.GetPoint()->GetNode().GetContentNode()->getLayoutFrame(rCursor.GetShell()->GetLayout()) &&
        (rCursor.GetMark()->GetNode() == rCursor.GetPoint()->GetNode() ||
        (rCursor.GetMark()->GetNode().IsContentNode() &&
         rCursor.GetMark()->GetNode().GetContentNode()->getLayoutFrame(rCursor.GetShell()->GetLayout())))))
    {
        return;
    }

    auto [pStartPos, pEndPos] = rCursor.StartEnd(); // SwPosition*

    lcl_LOKInvalidateFrames(*(pStartPos->GetNode().GetContentNode()),
                            rCursor.GetShell()->GetLayout(),
                            FRM_CNTNT, &rCursor.GetSttPos());

    lcl_LOKInvalidateFrames(*(pEndPos->GetNode().GetContentNode()),
                            rCursor.GetShell()->GetLayout(),
                            FRM_CNTNT, &rCursor.GetEndPos());
}

bool lcl_LOKRedlineNotificationEnabled()
{
    static bool bDisableRedlineComments = getenv("DISABLE_REDLINE") != nullptr;
    if (comphelper::LibreOfficeKit::isActive() && !bDisableRedlineComments)
        return true;

    return false;
}

// anonymous namespace

void SwRedlineTable::setMovedIDIfNeeded(sal_uInt32 nMax)
{
    if (nMax > m_nMaxMovedID)
        m_nMaxMovedID = nMax;
}

/// Emits LOK notification about one addition / removal of a redline item.
void SwRedlineTable::LOKRedlineNotification(RedlineNotification nType, SwRangeRedline* pRedline)
{
    // Disable since usability is very low beyond some small number of changes.
    if (!lcl_LOKRedlineNotificationEnabled())
        return;

    boost::property_tree::ptree aRedline;
    aRedline.put("action", (nType == RedlineNotification::Add ? "Add" :
                            (nType == RedlineNotification::Remove ? "Remove" :
                             (nType == RedlineNotification::Modify ? "Modify" : "???"))));
    aRedline.put("index", pRedline->GetId());
    aRedline.put("author", pRedline->GetAuthorString(1).toUtf8().getStr());
    aRedline.put("type", SwRedlineTypeToOUString(pRedline->GetRedlineData().GetType()).toUtf8().getStr());
    aRedline.put("comment", pRedline->GetRedlineData().GetComment().toUtf8().getStr());
    aRedline.put("description", pRedline->GetDescr().toUtf8().getStr());
    OUString sDateTime = utl::toISO8601(pRedline->GetRedlineData().GetTimeStamp().GetUNODateTime());
    aRedline.put("dateTime", sDateTime.toUtf8().getStr());

    auto [pStartPos, pEndPos] = pRedline->StartEnd(); // SwPosition*
    SwContentNode* pContentNd = pRedline->GetPointContentNode();
    SwView* pView = dynamic_cast<SwView*>(SfxViewShell::Current());
    if (pView && pContentNd)
    {
        SwShellCursor aCursor(pView->GetWrtShell(), *pStartPos);
        aCursor.SetMark();
        *aCursor.GetMark() = *pEndPos;

        aCursor.FillRects();

        SwRects* pRects(&aCursor);
        std::vector<OString> aRects;
        for(const SwRect& rNextRect : *pRects)
            aRects.push_back(rNextRect.SVRect().toString());

        const OString sRects = comphelper::string::join("; ", aRects);
        aRedline.put("textRange", sRects.getStr());

        lcl_LOKInvalidateStartEndFrames(aCursor);

        // When this notify method is called text invalidation is not done yet
        // Calling FillRects updates the text area so invalidation will not run on the correct rects
        // So we need to do an own invalidation here. It invalidates text frames containing the redlining
        SwDoc& rDoc = pRedline->GetDoc();
        SwViewShell* pSh;
        if( !rDoc.IsInDtor() )
        {
            pSh = rDoc.getIDocumentLayoutAccess().GetCurrentViewShell();
            if( pSh )
                for(SwNodeIndex nIdx(pStartPos->GetNode()); nIdx <= pEndPos->GetNode(); ++nIdx)
                {
                    SwContentNode* pContentNode = nIdx.GetNode().GetContentNode();
                    if (pContentNode)
                        pSh->InvalidateWindows(pContentNode->FindLayoutRect());
                }
        }
    }

    boost::property_tree::ptree aTree;
    aTree.add_child("redline", aRedline);
    std::stringstream aStream;
    boost::property_tree::write_json(aStream, aTree);
    std::string aPayload = aStream.str();

    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        if (pView && pView->GetDocId() == pViewShell->GetDocId())
            pViewShell->libreOfficeKitViewCallback(nType == RedlineNotification::Modify ? LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED : LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED, OString(aPayload));
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
}

bool SwRedlineTable::Insert(SwRangeRedline*& p)
{
    if( p->HasValidRange() )
    {
        std::pair<vector_type::const_iterator, bool> rv = maVector.insert( p );
        size_type nP = rv.first - begin();
        LOKRedlineNotification(RedlineNotification::Add, p);

        // detect text moving by checking nearby redlines, except during Undo
        // (apply isMoved() during OpenDocument and DOCX import, too, to fix
        // missing text moving handling in ODF and e.g. web version of MSO)
        if ( p->GetDoc().GetIDocumentUndoRedo().DoesUndo() ||
             p->GetDoc().IsInWriterfilterImport() ||
             p->GetDoc().IsInXMLImport() )
        {
            isMoved(nP);
        }

        p->CallDisplayFunc(nP);
        if (rv.second)
        {
            CheckOverlapping(rv.first);
            if (!mpMaxEndPos || (*(*rv.first)->End()) > *mpMaxEndPos->End())
                mpMaxEndPos = *rv.first;
        }
        return rv.second;
    }
    return InsertWithValidRanges( p );
}

void SwRedlineTable::CheckOverlapping(vector_type::const_iterator it)
{
    if (m_bHasOverlappingElements)
        return;
    if (maVector.size() <= 1) // a single element cannot be overlapping
        return;
    auto pCurr = *it;
    auto itNext = it + 1;
    if (itNext != maVector.end())
    {
        auto pNext = *itNext;
        if (pCurr->End()->GetNodeIndex() >= pNext->Start()->GetNodeIndex())
        {
            m_bHasOverlappingElements = true;
            return;
        }
    }
    if (it != maVector.begin())
    {
        auto pPrev = *(it - 1);
        if (pPrev->End()->GetNodeIndex() >= pCurr->Start()->GetNodeIndex())
            m_bHasOverlappingElements = true;
    }
}

bool SwRedlineTable::Insert(SwRangeRedline*& p, size_type& rP)
{
    if( p->HasValidRange() )
    {
        std::pair<vector_type::const_iterator, bool> rv = maVector.insert( p );
        rP = rv.first - begin();
        p->CallDisplayFunc(rP);
        if (rv.second)
        {
            CheckOverlapping(rv.first);
            if (!mpMaxEndPos || (*(*rv.first)->End()) > *mpMaxEndPos->End())
                mpMaxEndPos = *rv.first;
        }
        return rv.second;
    }
    return InsertWithValidRanges( p, &rP );
}

namespace sw {

std::vector<std::unique_ptr<SwRangeRedline>> GetAllValidRanges(std::unique_ptr<SwRangeRedline> p)
{
    std::vector<std::unique_ptr<SwRangeRedline>> ret;
    // Create valid "sub-ranges" from the Selection
    auto [pStart, pEnd] = p->StartEnd(); // SwPosition*
    SwPosition aNewStt( *pStart );
    SwNodes& rNds = aNewStt.GetNodes();
    SwContentNode* pC;

    if( !aNewStt.GetNode().IsContentNode() )
    {
        pC = SwNodes::GoNext(&aNewStt);
        if( !pC )
            aNewStt.Assign(rNds.GetEndOfContent());
    }


    if( aNewStt >= *pEnd )
        return ret;

    std::unique_ptr<SwRangeRedline> pNew;
    do {
        if( !pNew )
            pNew.reset(new SwRangeRedline( p->GetRedlineData(), aNewStt ));
        else
        {
            pNew->DeleteMark();
            *pNew->GetPoint() = aNewStt;
        }

        pNew->SetMark();
        GoEndSection( pNew->GetPoint() );
        // i60396: If the redlines starts before a table but the table is the last member
        // of the section, the GoEndSection will end inside the table.
        // This will result in an incorrect redline, so we've to go back
        SwNode* pTab = pNew->GetPoint()->GetNode().StartOfSectionNode()->FindTableNode();
        // We end in a table when pTab != 0
        if( pTab && !pNew->GetMark()->GetNode().StartOfSectionNode()->FindTableNode() )
        { // but our Mark was outside the table => Correction
            do
            {
                // We want to be before the table
                pNew->GetPoint()->Assign(*pTab);
                pC = GoPreviousPos( pNew->GetPoint(), false ); // here we are.
                if( pC )
                    pNew->GetPoint()->SetContent( 0 );
                pTab = pNew->GetPoint()->GetNode().StartOfSectionNode()->FindTableNode();
            } while( pTab ); // If there is another table we have to repeat our step backwards
        }

        // insert dummy character to the empty table rows to keep their changes
        SwNode& rBoxNode = pNew->GetMark()->GetNode();
        if ( rBoxNode.GetDoc().GetIDocumentUndoRedo().DoesUndo() && rBoxNode.GetTableBox() &&
             rBoxNode.GetTableBox()->GetUpper()->IsEmpty() && rBoxNode.GetTextNode() )
        {
            ::sw::UndoGuard const undoGuard(rBoxNode.GetDoc().GetIDocumentUndoRedo());
            rBoxNode.GetTextNode()->InsertDummy();
            pNew->GetMark()->SetContent( 1 );
        }

        if( *pNew->GetPoint() > *pEnd )
        {
            pC = nullptr;
            if( aNewStt.GetNode() != pEnd->GetNode() )
                do {
                    SwNode& rCurNd = aNewStt.GetNode();
                    if( rCurNd.IsStartNode() )
                    {
                        if( rCurNd.EndOfSectionIndex() < pEnd->GetNodeIndex() )
                            aNewStt.Assign( *rCurNd.EndOfSectionNode() );
                        else
                            break;
                    }
                    else if( rCurNd.IsContentNode() )
                        pC = rCurNd.GetContentNode();
                    aNewStt.Adjust(SwNodeOffset(1));
                } while( aNewStt.GetNodeIndex() < pEnd->GetNodeIndex() );

            if( aNewStt.GetNode() == pEnd->GetNode() )
                aNewStt.SetContent(pEnd->GetContentIndex());
            else if( pC )
            {
                aNewStt.Assign(*pC, pC->Len() );
            }

            if( aNewStt <= *pEnd )
                *pNew->GetPoint() = aNewStt;
        }
        else
            aNewStt = *pNew->GetPoint();
#if OSL_DEBUG_LEVEL > 0
        CheckPosition( pNew->GetPoint(), pNew->GetMark() );
#endif

        if( *pNew->GetPoint() != *pNew->GetMark() &&
            pNew->HasValidRange())
        {
            ret.push_back(std::move(pNew));
        }

        if( aNewStt >= *pEnd )
            break;
        pC = SwNodes::GoNext(&aNewStt);
        if( !pC )
            break;
    } while( aNewStt < *pEnd );

    return ret;
}

// namespace sw

static void lcl_setRowNotTracked(SwNode& rNode)
{
    SwDoc& rDoc = rNode.GetDoc();
    const SwTableBox* pTableBox = rNode.GetTableBox();
    if ( rDoc.GetIDocumentUndoRedo().DoesUndo() && pTableBox )
    {
        SvxPrintItem aSetTracking(RES_PRINT, false);
        SwNodeIndex aInsPos( *(pTableBox->GetSttNd()), 1);
        SwCursor aCursor( SwPosition(aInsPos), nullptr );
        ::sw::UndoGuard const undoGuard(rNode.GetDoc().GetIDocumentUndoRedo());
        rDoc.SetRowNotTracked( aCursor, aSetTracking );
    }
}

bool SwRedlineTable::InsertWithValidRanges(SwRangeRedline*& p, size_type* pInsPos)
{
    bool bAnyIns = false;
    bool bInsert = RedlineType::Insert == p->GetType();
    SwNode* pSttNode = &p->Start()->GetNode();

    std::vector<std::unique_ptr<SwRangeRedline>> redlines(
            GetAllValidRanges(std::unique_ptr<SwRangeRedline>(p)));

    // tdf#147180 set table change tracking in the empty row with text insertion
    if ( bInsert )
        lcl_setRowNotTracked(*pSttNode);

    for (std::unique_ptr<SwRangeRedline> & pRedline : redlines)
    {
        assert(pRedline->HasValidRange());
        size_type nInsPos;
        auto pTmpRedline = pRedline.release();
        if (Insert(pTmpRedline, nInsPos))
        {
            // tdf#147180 set table tracking to the table row
            lcl_setRowNotTracked(pTmpRedline->GetPointNode());

            pTmpRedline->CallDisplayFunc(nInsPos);
            bAnyIns = true;
            if (pInsPos && *pInsPos < nInsPos)
            {
                *pInsPos = nInsPos;
            }
        }
    }
    p = nullptr;
    return bAnyIns;
}

bool CompareSwRedlineTable::operator()(const SwRangeRedline* lhs, const SwRangeRedline* rhs) const
{
    return *lhs < *rhs;
}

SwRedlineTable::~SwRedlineTable()
{
   maVector.DeleteAndDestroyAll();
}

SwRedlineTable::size_type SwRedlineTable::GetPos(const SwRangeRedline* p) const
{
    vector_type::const_iterator it = maVector.find(p);
    if( it == maVector.end() )
        return npos;
    return it - maVector.begin();
}

void SwRedlineTable::Remove( const SwRangeRedline* p )
{
    const size_type nPos = GetPos(p);
    if (nPos == npos)
        return;
    Remove(nPos);
}

void SwRedlineTable::Remove( size_type nP )
{
    LOKRedlineNotification(RedlineNotification::Remove, maVector[nP]);
    SwDoc* pDoc = nullptr;
    if( !nP && 1 == size() )
        pDoc = &maVector.front()->GetDoc();

    if (mpMaxEndPos == maVector[nP])
        mpMaxEndPos = nullptr;
    maVector.erase( maVector.begin() + nP );

    if( pDoc && !pDoc->IsInDtor() )
    {
        SwViewShell* pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell();
        if( pSh )
            pSh->InvalidateWindows( SwRect( 0, 0, SAL_MAX_INT32, SAL_MAX_INT32 ) );
    }
}

void SwRedlineTable::DeleteAndDestroyAll()
{
    while (!maVector.empty())
    {
        auto const pRedline = maVector.back();
        maVector.erase_at(maVector.size() - 1);
        LOKRedlineNotification(RedlineNotification::Remove, pRedline);
        delete pRedline;
    }
    m_bHasOverlappingElements = false;
    mpMaxEndPos = nullptr;
}

void SwRedlineTable::DeleteAndDestroy(size_type const nP)
{
    auto const pRedline = maVector[nP];
    if (pRedline == mpMaxEndPos)
        mpMaxEndPos = nullptr;
    maVector.erase(maVector.begin() + nP);
    LOKRedlineNotification(RedlineNotification::Remove, pRedline);
    delete pRedline;
}

SwRedlineTable::size_type SwRedlineTable::FindNextOfSeqNo( size_type nSttPos ) const
{
    return nSttPos + 1 < size()
                ? FindNextSeqNo( operator[]( nSttPos )->GetSeqNo(), nSttPos+1 )
                : npos;
}

SwRedlineTable::size_type SwRedlineTable::FindPrevOfSeqNo( size_type nSttPos ) const
{
    return nSttPos ? FindPrevSeqNo( operator[]( nSttPos )->GetSeqNo(), nSttPos-1 )
                   : npos;
}

/// Find the next or preceding Redline with the same seq.no.
/// We can limit the search using look ahead (0 searches the whole array).
SwRedlineTable::size_type SwRedlineTable::FindNextSeqNo( sal_uInt16 nSeqNo, size_type nSttPos ) const
{
    auto constexpr nLookahead = 20;
    size_type nRet = npos;
    if( nSeqNo && nSttPos < size() )
    {
        size_type nEnd = size();
        const size_type nTmp = nSttPos + nLookahead;
        if (nTmp < nEnd)
        {
            nEnd = nTmp;
        }

        for( ; nSttPos < nEnd; ++nSttPos )
            if( nSeqNo == operator[]( nSttPos )->GetSeqNo() )
            {
                nRet = nSttPos;
                break;
            }
    }
    return nRet;
}

SwRedlineTable::size_type SwRedlineTable::FindPrevSeqNo( sal_uInt16 nSeqNo, size_type nSttPos ) const
{
    auto constexpr nLookahead = 20;
    size_type nRet = npos;
    if( nSeqNo && nSttPos < size() )
    {
        size_type nEnd = 0;
        if( nSttPos > nLookahead )
            nEnd = nSttPos - nLookahead;

        ++nSttPos;
        while( nSttPos > nEnd )
        {
            --nSttPos;
            if( nSeqNo == operator[](nSttPos)->GetSeqNo() )
            {
                nRet = nSttPos;
                break;
            }
        }
    }
    return nRet;
}

const SwRangeRedline* SwRedlineTable::FindAtPosition( const SwPosition& rSttPos,
                                        size_type& rPos,
                                        bool bNext ) const
{
    const SwRangeRedline* pFnd = nullptr;
    for( ; rPos < maVector.size() ; ++rPos )
    {
        const SwRangeRedline* pTmp = (*this)[ rPos ];
        if( pTmp->HasMark() && pTmp->IsVisible() )
        {
            auto [pRStt, pREnd] = pTmp->StartEnd(); // SwPosition*
            if( bNext ? *pRStt <= rSttPos : *pRStt < rSttPos )
            {
                if( bNext ? *pREnd > rSttPos : *pREnd >= rSttPos )
                {
                    pFnd = pTmp;
                    break;
                }
            }
            else
                break;
        }
    }
    return pFnd;
}

namespace
{
bool lcl_CanCombineWithRange(const SwRangeRedline* pOrigin, SwRangeRedline* pActual,
                             SwRangeRedline* pOther, bool bReverseDir, bool bCheckChilds)
{
    if (pOrigin->IsVisible() != pOther->IsVisible())
        return false;

    if (bReverseDir)
    {
        if (*(pOther->End()) != *(pActual->Start()))
            return false;
    }
    else
    {
        if (*(pActual->End()) != *(pOther->Start()))
            return false;
    }

    if (!pOrigin->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(0)))
    {
        if (!bCheckChilds)
        {
            return false;
        }

        // See if pOrigin and pOther can be combined because one redline data can combine with the
        // underlying redline data of the other redline.
        bool bChildCanCombine = false;
        if (pOther->GetStackCount() > 1
            && pOrigin->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(1)))
        {
            bChildCanCombine = true;
        }

        if (!bChildCanCombine && pOrigin->GetStackCount() > 1
            && pOther->GetRedlineData(0).CanCombineForAcceptReject(pOrigin->GetRedlineData(1)))
        {
            bChildCanCombine = true;
        }

        if (!bChildCanCombine)
        {
            return false;
        }
    }
    if (pOther->Start()->GetNode().StartOfSectionNode()
        != pActual->Start()->GetNode().StartOfSectionNode())
        return false;

    return true;
}
}

const SwPosition& SwRedlineTable::GetMaxEndPos() const
{
    assert(!empty() && "cannot call this when the redline table is empty");
    if (mpMaxEndPos)
        return *mpMaxEndPos->End();
    for (const SwRangeRedline* i : maVector)
    {
        if (!mpMaxEndPos || *i->End() > *mpMaxEndPos->End())
            mpMaxEndPos = i;
    }
    assert(mpMaxEndPos);
    return *mpMaxEndPos->End();
}

void SwRedlineTable::getConnectedArea(size_type nPosOrigin, size_type& rPosStart,
                                      size_type& rPosEnd, bool bCheckChilds) const
{
    // Keep the original redline .. else we should memorize which children was checked
    // at the last combined redline.
    SwRangeRedline* pOrigin = (*this)[nPosOrigin];
    rPosStart = nPosOrigin;
    rPosEnd = nPosOrigin;
    SwRangeRedline* pRedline = pOrigin;
    SwRangeRedline* pOther;

    // connection info is already here..only the actual text is missing at import time
    // so no need to check Redline->GetContentIdx() here yet.
    while (rPosStart > 0 && (pOther = (*this)[rPosStart - 1])
           && lcl_CanCombineWithRange(pOrigin, pRedline, pOther, true, bCheckChilds))
    {
        rPosStart--;
        pRedline = pOther;
    }
    pRedline = pOrigin;
    while (rPosEnd + 1 < size() && (pOther = (*this)[rPosEnd + 1])
           && lcl_CanCombineWithRange(pOrigin, pRedline, pOther, false, bCheckChilds))
    {
        rPosEnd++;
        pRedline = pOther;
    }
}

OUString SwRedlineTable::getTextOfArea(size_type rPosStart, size_type rPosEnd) const
{
    // Normally a SwPaM::GetText() would be enough with rPosStart-start and rPosEnd-end
    // But at import time some text is not present there yet
    // we have to collect them 1 by 1

    OUStringBuffer sRet(256);

    for (size_type nIdx = rPosStart; nIdx <= rPosEnd; ++nIdx)
    {
        SwRangeRedline* pRedline = (*this)[nIdx];

        if (nullptr == pRedline->GetContentIdx())
        {
            pRedline->AppendTextTo(sRet);
        }
        else // otherwise it is saved in pContentSect, e.g. during ODT import
        {
            SwPaM aTmpPaM(pRedline->GetContentIdx()->GetNode(),
                              *pRedline->GetContentIdx()->GetNode().EndOfSectionNode());
            if (!aTmpPaM.Start()->nNode.GetNode().GetTextNode())
            {
                OUString sNew = aTmpPaM.GetText();
                if (sNew[0] == CH_TXTATR_NEWLINE)
                    sRet.append(sNew.subView(1));
                else
                    sRet.append(sNew);
            }
            else
                aTmpPaM.AppendTextTo(sRet); // append contents of aTmpPaM to sRet
        }
    }

    return sRet.makeStringAndClear();
}

bool SwRedlineTable::isMoved(size_type rPos) const
{
    // If it is already a part of a movement, then don't check it.
    if ((*this)[rPos]->GetMoved() != 0)
        return false;
    // First try with single redline. then try with combined redlines
    if (isMovedImpl(rPos, false))
        return true;
    else
        return isMovedImpl(rPos, true);
}

bool SwRedlineTable::isMovedImpl(size_type rPos, bool bTryCombined) const
{
    bool bRet = false;
    auto constexpr nLookahead = 20;
    SwRangeRedline* pRedline = (*this)[ rPos ];

    // set redline type of the searched pair
    RedlineType nPairType = pRedline->GetType();
    if ( RedlineType::Delete == nPairType )
        nPairType = RedlineType::Insert;
    else if ( RedlineType::Insert == nPairType )
        nPairType = RedlineType::Delete;
    else
        // only deleted or inserted text can be moved
        return false;

    OUString sTrimmed;
    SwRedlineTable::size_type nPosStart = rPos;
    SwRedlineTable::size_type nPosEnd = rPos;

    if (bTryCombined)
    {
        getConnectedArea(rPos, nPosStart, nPosEnd, false);
        if (nPosStart != nPosEnd)
            sTrimmed = getTextOfArea(nPosStart, nPosEnd).trim();
    }

    if (sTrimmed.isEmpty())
    {
        // if this redline is visible the content is in this PaM
        if (nullptr == pRedline->GetContentIdx())
        {
            sTrimmed = pRedline->GetText().trim();
        }
        else // otherwise it is saved in pContentSect, e.g. during ODT import
        {
            SwPaM aTmpPaM(pRedline->GetContentIdx()->GetNode(),
                             *pRedline->GetContentIdx()->GetNode().EndOfSectionNode());
            sTrimmed = aTmpPaM.GetText().trim();
        }
    }

    // detection of move needs at least 6 characters with an inner
    // space after stripping white spaces of the redline to skip
    // frequent deleted and inserted articles or other common
    // word parts, e.g. 'the' and 'of a' to detect as text moving
    if (sTrimmed.getLength() < 6 || sTrimmed.indexOf(' ') == -1)
    {
        return false;
    }

    // Todo: lessen the previous condition..:
    // if the source / destination is a whole node change then maybe space is not needed

    // search pair around the actual redline
    size_type nEnd = rPos + nLookahead < size()
        ? rPos + nLookahead
        : size();
    size_type nStart = rPos > nLookahead ? rPos - nLookahead : 0;
    // first, try to compare to single redlines
    // next, try to compare to combined redlines
    for (int nPass = 0; nPass < 2 && !bRet; nPass++)
    {
        for (size_type nPosAct = nStart; nPosAct < nEnd && !bRet; ++nPosAct)
        {
            SwRangeRedline* pPair = (*this)[nPosAct];

            // redline must be the requested type and from the same author
            if (nPairType != pPair->GetType() || pRedline->GetAuthor() != pPair->GetAuthor())
            {
                continue;
            }

            OUString sPairTrimmed = u""_ustr;
            SwRedlineTable::size_type nPairStart = nPosAct;
            SwRedlineTable::size_type nPairEnd = nPosAct;

            if (nPass == 0)
            {
                // if this redline is visible the content is in this PaM
                if (nullptr == pPair->GetContentIdx())
                {
                    sPairTrimmed = o3tl::trim(pPair->GetText());
                }
                else // otherwise it is saved in pContentSect, e.g. during ODT import
                {
                    // saved in pContentSect, e.g. during ODT import
                    SwPaM aPairPaM(pPair->GetContentIdx()->GetNode(),
                                         *pPair->GetContentIdx()->GetNode().EndOfSectionNode());
                    sPairTrimmed = o3tl::trim(aPairPaM.GetText());
                }
            }
            else
            {
                getConnectedArea(nPosAct, nPairStart, nPairEnd, false);
                if (nPairStart != nPairEnd)
                    sPairTrimmed = getTextOfArea(nPairStart, nPairEnd).trim();
            }

            // pair at tracked moving: same text by trimming trailing white spaces
            if (abs(sTrimmed.getLength() - sPairTrimmed.getLength()) <= 2
                && sTrimmed == sPairTrimmed)
            {
                sal_uInt32 nMID = getNewMovedID();
                if (nPosStart != nPosEnd)
                {
                    for (size_type nIdx = nPosStart; nIdx <= nPosEnd; ++nIdx)
                    {
                        (*this)[nIdx]->SetMoved(nMID);
                        if (nIdx != rPos)
                            (*this)[nIdx]->InvalidateRange(SwRangeRedline::Invalidation::Add);
                    }
                }
                else
                    pRedline->SetMoved(nMID);

                //in (nPass == 0) it will only call once .. as nPairStart == nPairEnd == nPosAct
                for (size_type nIdx = nPairStart; nIdx <= nPairEnd; ++nIdx)
                {
                    (*this)[nIdx]->SetMoved(nMID);
                    (*this)[nIdx]->InvalidateRange(SwRangeRedline::Invalidation::Add);
                }

                bRet = true;
            }

            //we can skip the combined redlines
            if (nPass == 1)
                nPosAct = nPairEnd;
        }
    }

    return bRet;
}

void SwRedlineTable::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwRedlineTable"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p"this);

    for (SwRedlineTable::size_type nCurRedlinePos = 0; nCurRedlinePos < size(); ++nCurRedlinePos)
        operator[](nCurRedlinePos)->dumpAsXml(pWriter);

    (void)xmlTextWriterEndElement(pWriter);
}

SwRedlineExtraData::~SwRedlineExtraData()
{
}

void SwRedlineExtraData::Reject( SwPaM& ) const
{
}

bool SwRedlineExtraData::operator == ( const SwRedlineExtraData& ) const
{
    return false;
}

SwRedlineExtraData_FormatColl::SwRedlineExtraData_FormatColl( UIName aColl,
                                                sal_uInt16 nPoolFormatId,
                                                const std::shared_ptr<SfxItemSet>& pItemSet,
                                                bool bFormatAll )
    : m_sFormatNm(std::move(aColl)), m_nPoolId(nPoolFormatId), m_bFormatAll(bFormatAll)
{
    if( pItemSet && pItemSet->Count() )
        m_pSet = pItemSet;
}

SwRedlineExtraData_FormatColl::~SwRedlineExtraData_FormatColl()
{
}

SwRedlineExtraData* SwRedlineExtraData_FormatColl::CreateNew() const
{
    return new SwRedlineExtraData_FormatColl( m_sFormatNm, m_nPoolId, m_pSet, m_bFormatAll );
}

void SwRedlineExtraData_FormatColl::Reject( SwPaM& rPam ) const
{
    SwDoc& rDoc = rPam.GetDoc();

    // What about Undo? Is it turned off?
    SwTextFormatColl* pColl = USHRT_MAX == m_nPoolId
                            ? rDoc.FindTextFormatCollByName( m_sFormatNm )
                            : rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( m_nPoolId );

    RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore));

    SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() );

    const SwPosition* pEnd = rPam.End();

    if ( !m_bFormatAll || pEnd->GetContentIndex() == 0 )
    {
        // don't reject the format of the next paragraph (that is handled by the next redline)
        if (aPam.GetPoint()->GetNode() > aPam.GetMark()->GetNode())
        {
            aPam.GetPoint()->Adjust(SwNodeOffset(-1));
            SwContentNode* pNode = aPam.GetPoint()->GetNode().GetContentNode();
            if ( pNode )
                aPam.GetPoint()->SetContent( pNode->Len() );
            else
                // tdf#147507 set it back to a content node to avoid of crashing
                aPam.GetPoint()->Adjust(SwNodeOffset(+1));
        }
        else if (aPam.GetPoint()->GetNode() < aPam.GetMark()->GetNode())
        {
            aPam.GetMark()->Adjust(SwNodeOffset(-1));
            SwContentNode* pNode = aPam.GetMark()->GetNode().GetContentNode();
            aPam.GetMark()->SetContent( pNode->Len() );
        }
    }

    if( pColl )
        rDoc.SetTextFormatColl( aPam, pColl, false );

    if( m_pSet )
        rDoc.getIDocumentContentOperations().InsertItemSet( aPam, *m_pSet );

    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
}

bool SwRedlineExtraData_FormatColl::operator == ( const SwRedlineExtraData& r) const
{
    const SwRedlineExtraData_FormatColl& rCmp = static_cast<const SwRedlineExtraData_FormatColl&>(r);
    return m_sFormatNm == rCmp.m_sFormatNm && m_nPoolId == rCmp.m_nPoolId &&
            m_bFormatAll == rCmp.m_bFormatAll &&
            ( ( !m_pSet && !rCmp.m_pSet ) ||
               ( m_pSet && rCmp.m_pSet && *m_pSet == *rCmp.m_pSet ) );
}

void SwRedlineExtraData_FormatColl::SetItemSet( const std::shared_ptr<SfxItemSet>& ;pSet )
{
    if( pSet && pSet->Count() )
        m_pSet = pSet;
    else
        m_pSet.reset();
}

SwRedlineExtraData_Format::SwRedlineExtraData_Format( const SfxItemSet& rSet )
{
    SfxItemIter aIter( rSet );
    for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem())
    {
        m_aWhichIds.push_back( pItem->Which() );
    }
}

SwRedlineExtraData_Format::SwRedlineExtraData_Format(
        const SwRedlineExtraData_Format& rCpy )
    : SwRedlineExtraData()
{
    m_aWhichIds.insert( m_aWhichIds.begin(), rCpy.m_aWhichIds.begin(), rCpy.m_aWhichIds.end() );
}

SwRedlineExtraData_Format::~SwRedlineExtraData_Format()
{
}

SwRedlineExtraData* SwRedlineExtraData_Format::CreateNew() const
{
    return new SwRedlineExtraData_Format( *this );
}

void SwRedlineExtraData_Format::Reject( SwPaM& rPam ) const
{
    SwDoc& rDoc = rPam.GetDoc();

    RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore));

    // Actually we need to reset the Attribute here!
    forconst auto& rWhichId : m_aWhichIds )
    {
        rDoc.getIDocumentContentOperations().InsertPoolItem( rPam, *GetDfltAttr( rWhichId ),
            SetAttrMode::DONTEXPAND );
    }

    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
}

bool SwRedlineExtraData_Format::operator == ( const SwRedlineExtraData& rCmp ) const
{
    const size_t nEnd = m_aWhichIds.size();
    if( nEnd != static_cast<const SwRedlineExtraData_Format&>(rCmp).m_aWhichIds.size() )
        return false;

    for( size_t n = 0; n < nEnd; ++n )
    {
        ifstatic_cast<const SwRedlineExtraData_Format&>(rCmp).m_aWhichIds[n] != m_aWhichIds[n])
        {
            return false;
        }
    }
    return true;
}

void SwRedlineSaveDatas::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwRedlineSaveDatas"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p"this);

    for (const auto& rRedlineData : m_Data)
    {
        (void)xmlTextWriterStartElement(pWriter, BAD_CAST("data"));
        const SwRedlineData* pData = rRedlineData.get();
        while (pData)
        {
            pData->dumpAsXml(pWriter);
            pData = pData->Next();
        }
        (void)xmlTextWriterEndElement(pWriter);
    }

    (void)xmlTextWriterEndElement(pWriter);
}

SwRedlineData::SwRedlineData( RedlineType eT, std::size_t nAut, sal_uInt32 nMovedID )
    : m_pNext( nullptr ), m_pExtraData( nullptr ),
    m_aStamp( DateTime::SYSTEM ),
    m_nAuthor( nAut ), m_eType( eT ), m_nSeqNo( 0 ), m_bAutoFormat(false), m_nMovedID(nMovedID)
{
    m_aStamp.SetNanoSec( 0 );
}

SwRedlineData::SwRedlineData(
    const SwRedlineData& rCpy,
    bool bCpyNext )
    : m_pNext( ( bCpyNext && rCpy.m_pNext ) ? new SwRedlineData( *rCpy.m_pNext ) : nullptr )
    , m_pExtraData( rCpy.m_pExtraData ? rCpy.m_pExtraData->CreateNew() : nullptr )
    , m_sComment( rCpy.m_sComment )
    , m_aStamp( rCpy.m_aStamp )
    , m_nAuthor( rCpy.m_nAuthor )
    , m_eType( rCpy.m_eType )
    , m_nSeqNo( rCpy.m_nSeqNo )
    , m_bAutoFormat(false)
    , m_nMovedID( rCpy.m_nMovedID )
{
}

// For sw3io: We now own pNext!
SwRedlineData::SwRedlineData(RedlineType eT, std::size_t nAut, const DateTime& rDT,
    sal_uInt32 nMovedID, OUString aCmnt, SwRedlineData *pNxt)
    : m_pNext(pNxt), m_pExtraData(nullptr), m_sComment(std::move(aCmnt)), m_aStamp(rDT),
    m_nAuthor(nAut), m_eType(eT), m_nSeqNo(0), m_bAutoFormat(false), m_nMovedID(nMovedID)
{
}

SwRedlineData::~SwRedlineData()
{
    delete m_pExtraData;
    delete m_pNext;
}

// Check whether the absolute difference between the two dates is no larger than one minute (can
// give inaccurate results if at least one of the dates is not valid/normalized):
static bool deltaOneMinute(DateTime const & t1, DateTime const & t2) {
    auto const [min, max] = std::minmax(t1, t2);
    // Avoid overflow of `min + tools::Time(0, 1)` below when min is close to the maximum valid
    // DateTime:
    if (min >= DateTime({31, 12, std::numeric_limits<sal_Int16>::max()}, {23, 59})) {
        return true;
    }
    return max <= min + tools::Time(0, 1);
}

bool SwRedlineData::CanCombine(const SwRedlineData& rCmp) const
{
    return m_nAuthor == rCmp.m_nAuthor &&
            m_eType == rCmp.m_eType &&
            m_sComment == rCmp.m_sComment &&
            deltaOneMinute(GetTimeStamp(), rCmp.GetTimeStamp()) &&
            m_nMovedID == rCmp.m_nMovedID &&
            (( !m_pNext && !rCmp.m_pNext ) ||
                ( m_pNext && rCmp.m_pNext &&
                m_pNext->CanCombine( *rCmp.m_pNext ))) &&
            (( !m_pExtraData && !rCmp.m_pExtraData ) ||
                ( m_pExtraData && rCmp.m_pExtraData &&
                    *m_pExtraData == *rCmp.m_pExtraData ));
}

// Check if we could/should accept/reject the 2 redlineData at the same time.
// No need to check its children equality
bool SwRedlineData::CanCombineForAcceptReject(const SwRedlineData& rCmp) const
{
    return m_nAuthor == rCmp.m_nAuthor &&
            m_eType == rCmp.m_eType &&
            m_sComment == rCmp.m_sComment &&
            deltaOneMinute(GetTimeStamp(), rCmp.GetTimeStamp()) &&
            m_nMovedID == rCmp.m_nMovedID &&
            (( !m_pExtraData && !rCmp.m_pExtraData ) ||
                ( m_pExtraData && rCmp.m_pExtraData &&
                    *m_pExtraData == *rCmp.m_pExtraData ));
}

/// ExtraData is copied. The Pointer's ownership is thus NOT transferred
/// to the Redline Object!
void SwRedlineData::SetExtraData( const SwRedlineExtraData* pData )
{
    delete m_pExtraData;

    // Check if there is data - and if so - delete it
    if( pData )
        m_pExtraData = pData->CreateNew();
    else
        m_pExtraData = nullptr;
}

const TranslateId STR_REDLINE_ARY[] =
{
    STR_UNDO_REDLINE_INSERT,
    STR_UNDO_REDLINE_DELETE,
    STR_UNDO_REDLINE_FORMAT,
    STR_UNDO_REDLINE_TABLE,
    STR_UNDO_REDLINE_FMTCOLL,
    STR_UNDO_REDLINE_PARAGRAPH_FORMAT,
    STR_UNDO_REDLINE_TABLE_ROW_INSERT,
    STR_UNDO_REDLINE_TABLE_ROW_DELETE,
    STR_UNDO_REDLINE_TABLE_CELL_INSERT,
    STR_UNDO_REDLINE_TABLE_CELL_DELETE
};

OUString SwRedlineData::GetDescr() const
{
    return SwResId(STR_REDLINE_ARY[static_cast<int>(GetType())]);
}

void SwRedlineData::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwRedlineData"));

    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p"this);
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("id"), BAD_CAST(OString::number(GetSeqNo()).getStr()));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("author"), BAD_CAST(SwModule::get()->GetRedlineAuthor(GetAuthor()).toUtf8().getStr()));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("date"), BAD_CAST(DateTimeToOString(GetTimeStamp()).getStr()));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("descr"), BAD_CAST(GetDescr().toUtf8().getStr()));

    OString sRedlineType;
    switch (GetType())
    {
        case RedlineType::Insert:
            sRedlineType = "REDLINE_INSERT"_ostr;
            break;
        case RedlineType::Delete:
            sRedlineType = "REDLINE_DELETE"_ostr;
            break;
        case RedlineType::Format:
            sRedlineType = "REDLINE_FORMAT"_ostr;
            break;
        case RedlineType::Table:
            sRedlineType = "REDLINE_TABLE"_ostr;
            break;
        case RedlineType::FmtColl:
            sRedlineType = "REDLINE_FMTCOLL"_ostr;
            break;
        case RedlineType::ParagraphFormat:
            sRedlineType = "REDLINE_PARAGRAPH_FORMAT"_ostr;
            break;
        default:
            sRedlineType = "UNKNOWN"_ostr;
            break;
    }
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("type"), BAD_CAST(sRedlineType.getStr()));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("moved"), BAD_CAST(OString::number(m_nMovedID).getStr()));

    (void)xmlTextWriterEndElement(pWriter);
}

sal_uInt32 SwRangeRedline::s_nLastId = 1;

namespace
{
void lcl_LOKBroadcastCommentOperation(RedlineType type, const SwPaM& rPam)
{
    if (comphelper::LibreOfficeKit::isActive())
    {
        auto eHintType = RedlineType::Delete == type ? SwFormatFieldHintWhich::REDLINED_DELETION: SwFormatFieldHintWhich::INSERTED;
        const SwTextNode *pTextNode = rPam.GetPointNode().GetTextNode();
        SwTextAttr* pTextAttr = pTextNode ? pTextNode->GetFieldTextAttrAt(rPam.GetPoint()->GetContentIndex() - 1, ::sw::GetTextAttrMode::Default) : nullptr;
        SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pTextAttr));
        if (pTextField)
            const_cast<SwFormatField&>(pTextField->GetFormatField()).Broadcast(SwFormatFieldHint(&pTextField->GetFormatField(), eHintType));
    }
}
// anonymous namespace

SwRangeRedline::SwRangeRedline(RedlineType eTyp, const SwPaM& rPam, sal_uInt32 nMovedID )
    : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ), m_pRedlineData(
          new SwRedlineData(eTyp, GetDoc().getIDocumentRedlineAccess().GetRedlineAuthor(), nMovedID ) )
    ,
    m_nId( s_nLastId++ )
{
    GetBound().SetOwner(this);
    GetBound(false).SetOwner(this);

    m_bDelLastPara = false;
    m_bIsVisible = true;
    if( !rPam.HasMark() )
        DeleteMark();

    // set default comment for single annotations added or deleted
    if ( IsAnnotation() )
    {
        SetComment( RedlineType::Delete == eTyp
            ? SwResId(STR_REDLINE_COMMENT_DELETED)
            : SwResId(STR_REDLINE_COMMENT_ADDED) );

        lcl_LOKBroadcastCommentOperation(eTyp, rPam);
    }
}

SwRangeRedline::SwRangeRedline( const SwRedlineData& rData, const SwPaM& rPam )
    : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ),
    m_pRedlineData( new SwRedlineData( rData )),
    m_nId( s_nLastId++ )
{
    GetBound().SetOwner(this);
    GetBound(false).SetOwner(this);

    m_bDelLastPara = false;
    m_bIsVisible = true;
    if( !rPam.HasMark() )
        DeleteMark();

    // set default comment for single annotations added or deleted
    if ( IsAnnotation() )
    {
        SetComment( RedlineType::Delete == rData.m_eType
            ? SwResId(STR_REDLINE_COMMENT_DELETED)
            : SwResId(STR_REDLINE_COMMENT_ADDED) );

        lcl_LOKBroadcastCommentOperation(rData.m_eType, rPam);
    }
}

SwRangeRedline::SwRangeRedline( const SwRedlineData& rData, const SwPosition& rPos )
    : SwPaM( rPos ),
    m_pRedlineData( new SwRedlineData( rData )),
    m_nId( s_nLastId++ )
{
    GetBound().SetOwner(this);
    GetBound(false).SetOwner(this);

    m_bDelLastPara = false;
    m_bIsVisible = true;
}

SwRangeRedline::SwRangeRedline( const SwRangeRedline& rCpy )
    : SwPaM( *rCpy.GetMark(), *rCpy.GetPoint() ),
    m_pRedlineData( new SwRedlineData( *rCpy.m_pRedlineData )),
    m_nId( s_nLastId++ )
{
    GetBound().SetOwner(this);
    GetBound(false).SetOwner(this);

    m_bDelLastPara = false;
    m_bIsVisible = true;
    if( !rCpy.HasMark() )
        DeleteMark();
}

SwRangeRedline::~SwRangeRedline()
{
    if( m_oContentSect )
    {
        // delete the ContentSection
        if( !GetDoc().IsInDtor() )
            GetDoc().getIDocumentContentOperations().DeleteSection( &m_oContentSect->GetNode() );
        m_oContentSect.reset();
    }
    delete m_pRedlineData;
}

void MaybeNotifyRedlineModification(SwRangeRedline& rRedline, SwDoc& rDoc)
{
    if (!lcl_LOKRedlineNotificationEnabled())
        return;

    const SwRedlineTable& rRedTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
    for (SwRedlineTable::size_type i = 0; i < rRedTable.size(); ++i)
    {
        if (rRedTable[i] == &rRedline)
        {
            SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, &rRedline);
            break;
        }
    }
}

void SwRangeRedline::MaybeNotifyRedlinePositionModification(tools::Long nTop)
{
    if (!lcl_LOKRedlineNotificationEnabled())
        return;

    if(!m_oLOKLastNodeTop || *m_oLOKLastNodeTop != nTop)
    {
        m_oLOKLastNodeTop = nTop;
        SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, this);
    }
}

void SwRangeRedline::SetStart( const SwPosition& rPos, SwPosition* pSttPtr )
{
    if( !pSttPtr ) pSttPtr = Start();
    *pSttPtr = rPos;

    MaybeNotifyRedlineModification(*this, GetDoc());
}

void SwRangeRedline::SetEnd( const SwPosition& rPos, SwPosition* pEndPtr )
{
    if( !pEndPtr ) pEndPtr = End();
    *pEndPtr = rPos;

    MaybeNotifyRedlineModification(*this, GetDoc());
}

/// Do we have a valid Selection?
bool SwRangeRedline::HasValidRange() const
{
    const SwNode* pPtNd = &GetPoint()->GetNode(),
                * pMkNd = &GetMark()->GetNode();
    if( pPtNd->StartOfSectionNode() == pMkNd->StartOfSectionNode() &&
        !pPtNd->StartOfSectionNode()->IsTableNode() &&
        // invalid if points on the end of content
        // end-of-content only invalid if no content index exists
        ( pPtNd != pMkNd || GetContentIdx() != nullptr ||
          pPtNd != &pPtNd->GetNodes().GetEndOfContent() )
        )
        return true;
    return false;
}

void SwRangeRedline::CallDisplayFunc(size_t nMyPos)
{
    RedlineFlags eShow = RedlineFlags::ShowMask & GetDoc().getIDocumentRedlineAccess().GetRedlineFlags();
    if (eShow == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete))
        Show(0, nMyPos);
    else if (eShow == RedlineFlags::ShowInsert)
        Hide(0, nMyPos);
    else if (eShow == RedlineFlags::ShowDelete)
        ShowOriginal(0, nMyPos);
}

namespace
{
RedlineType GetRedlineTypeIgnoringAdditonalFormat(const SwRangeRedline& rRedline)
{
    RedlineType eType = rRedline.GetType();

    if (eType == RedlineType::Format && rRedline.GetStackCount() > 1
        && rRedline.GetType(1) == RedlineType::Delete)
    {
        // Consider format-on-delete the same as simple delete, so the range gets moved to the
        // "Deleted Change Tracking content" toplevel section from body content during file save.
        eType = RedlineType::Delete;
    }

    return eType;
}
}

void SwRangeRedline::Show(sal_uInt16 nLoop, size_t nMyPos, bool bForced)
{
    SwDoc& rDoc = GetDoc();

    bool bIsShowChangesInMargin = false;
    if ( !bForced )
    {
        SwViewShell* pSh = rDoc.getIDocumentLayoutAccess().GetCurrentViewShell();
        if (pSh)
            bIsShowChangesInMargin = pSh->GetViewOptions()->IsShowChangesInMargin();
        else
            bIsShowChangesInMargin = SwModule::get()->GetUsrPref(false)->IsShowChangesInMargin();
    }

    if( 1 > nLoop && !bIsShowChangesInMargin )
        return;

    RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);
    ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());

    switch (GetRedlineTypeIgnoringAdditonalFormat(*this))
    {
    case RedlineType::Insert:           // Content has been inserted
        m_bIsVisible = true;
        MoveFromSection(nMyPos);
        break;

    case RedlineType::Delete:           // Content has been deleted
        m_bIsVisible = !bIsShowChangesInMargin;

        if (m_bIsVisible)
            MoveFromSection(nMyPos);
        else
        {
            switch( nLoop )
            {
            case 0: MoveToSection();    break;
            case 1: CopyToSection();    break;
            case 2: DelCopyOfSection(nMyPos); break;
            }
        }
        break;

    case RedlineType::Format:           // Attributes have been applied
    case RedlineType::Table:            // Table structure has been modified
        InvalidateRange(Invalidation::Add);
        break;
    default:
        break;
    }
    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
}

void SwRangeRedline::Hide(sal_uInt16 nLoop, size_t nMyPos, bool /*bForced*/)
{
    SwDoc& rDoc = GetDoc();
    RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);
    ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());

    switch (GetRedlineTypeIgnoringAdditonalFormat(*this))
    {
    case RedlineType::Insert:           // Content has been inserted
        m_bIsVisible = true;
        if( 1 <= nLoop )
            MoveFromSection(nMyPos);
        break;

    case RedlineType::Delete:           // Content has been deleted
        m_bIsVisible = false;
        switch( nLoop )
        {
        case 0: MoveToSection();    break;
        case 1: CopyToSection();    break;
        case 2: DelCopyOfSection(nMyPos); break;
        }
        break;

    case RedlineType::Format:           // Attributes have been applied
    case RedlineType::Table:            // Table structure has been modified
        if( 1 <= nLoop )
            InvalidateRange(Invalidation::Remove);
        break;
    default:
        break;
    }
    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
}

void SwRangeRedline::ShowOriginal(sal_uInt16 nLoop, size_t nMyPos, bool /*bForced*/)
{
    SwDoc& rDoc = GetDoc();
    RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
    SwRedlineData* pCur;

    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);
    ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());

    // Determine the Type, it's the first on Stack
    for( pCur = m_pRedlineData; pCur->m_pNext; )
        pCur = pCur->m_pNext;

    switch( pCur->m_eType )
    {
    case RedlineType::Insert:           // Content has been inserted
        m_bIsVisible = false;
        switch( nLoop )
        {
        case 0: MoveToSection();    break;
        case 1: CopyToSection();    break;
        case 2: DelCopyOfSection(nMyPos); break;
        }
        break;

    case RedlineType::Delete:           // Content has been deleted
        m_bIsVisible = true;
        if( 1 <= nLoop )
            MoveFromSection(nMyPos);
        break;

    case RedlineType::Format:           // Attributes have been applied
    case RedlineType::Table:            // Table structure has been modified
        if( 1 <= nLoop )
            InvalidateRange(Invalidation::Remove);
        break;
    default:
        break;
    }
    rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
}

// trigger the Layout
void SwRangeRedline::InvalidateRange(Invalidation const eWhy)
{
    auto [pRStt, pREnd] = StartEnd(); // SwPosition*
    SwNodeOffset nSttNd = pRStt->GetNodeIndex(),
                 nEndNd = pREnd->GetNodeIndex();
    sal_Int32 nSttCnt = pRStt->GetContentIndex();
    sal_Int32 nEndCnt = pREnd->GetContentIndex();

    SwNodes& rNds = GetDoc().GetNodes();
    for (SwNodeOffset n(nSttNd); n <= nEndNd; ++n)
    {
        SwNode* pNode = rNds[n];

        if (pNode && pNode->IsTextNode())
        {
            SwTextNode* pNd = pNode->GetTextNode();

            SwUpdateAttr aHt(
                n == nSttNd ? nSttCnt : 0,
                n == nEndNd ? nEndCnt : pNd->GetText().getLength(),
                RES_UPDATEATTR_FMT_CHG);

            pNd->TriggerNodeUpdate(sw::UpdateAttrHint(&aHt, &aHt));

            // SwUpdateAttr must be handled first, otherwise indexes are off
            if (GetType() == RedlineType::Delete)
            {
                sal_Int32 const nStart(n == nSttNd ? nSttCnt : 0);
                sal_Int32 const nLen((n == nEndNd ? nEndCnt : pNd->GetText().getLength()) - nStart);
                if (eWhy == Invalidation::Add)
                {
                    sw::RedlineDelText const hint(nStart, nLen);
                    pNd->CallSwClientNotify(hint);
                }
                else
                {
                    sw::RedlineUnDelText const hint(nStart, nLen);
                    pNd->CallSwClientNotify(hint);
                }

                if (comphelper::LibreOfficeKit::isActive() && IsAnnotation())
                {
                    auto eHintType = eWhy == Invalidation::Add ? SwFormatFieldHintWhich::INSERTED: SwFormatFieldHintWhich::REMOVED;
                    const SwTextNode *pTextNode = this->GetPointNode().GetTextNode();
                    SwTextAttr* pTextAttr = pTextNode ? pTextNode->GetFieldTextAttrAt(this->GetPoint()->GetContentIndex() - 1, ::sw::GetTextAttrMode::Default) : nullptr;
                    SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pTextAttr));
                    if (pTextField)
                        const_cast<SwFormatField&>(pTextField->GetFormatField()).Broadcast(SwFormatFieldHint(&pTextField->GetFormatField(), eHintType));
                }
            }
        }
    }
}

/** Calculates the start and end position of the intersection rTmp and
    text node nNdIdx */

bool SwRangeRedline::CalcStartEnd(SwNodeOffset const nNdIdx,
        sal_Int32 & rStart, sal_Int32 & rEnd) const
{
    auto [pRStt, pREnd] = StartEnd(); // SwPosition*
    if( pRStt->GetNodeIndex() < nNdIdx )
    {
        if( pREnd->GetNodeIndex() > nNdIdx )
        {
            rStart = 0;             // Paragraph is completely enclosed
            rEnd = COMPLETE_STRING;
        }
        else if (pREnd->GetNodeIndex() == nNdIdx)
        {
            rStart = 0;             // Paragraph is overlapped in the beginning
            rEnd = pREnd->GetContentIndex();
        }
        else // redline ends before paragraph
        {
            rStart = COMPLETE_STRING;
            rEnd = COMPLETE_STRING;
            return true;
        }
    }
    else if( pRStt->GetNodeIndex() == nNdIdx )
    {
        rStart = pRStt->GetContentIndex();
        if( pREnd->GetNodeIndex() == nNdIdx )
            rEnd = pREnd->GetContentIndex(); // Within the Paragraph
        else
            rEnd = COMPLETE_STRING;      // Paragraph is overlapped in the end
    }
    else
    {
        rStart = COMPLETE_STRING;
        rEnd = COMPLETE_STRING;
    }
    return false;
}

static void lcl_storeAnnotationMarks(SwDoc& rDoc, const SwPosition* pStart, const SwPosition* pEnd)
{
    // tdf#115815 keep original start position of collapsed annotation ranges
    // as temporary bookmarks (removed after file saving and file loading)
    IDocumentMarkAccess& rDMA(*rDoc.getIDocumentMarkAccess());
    for (auto iter = rDMA.findFirstAnnotationMarkNotStartsBefore(*pStart);
          iter != rDMA.getAnnotationMarksEnd(); ++iter)
    {
        SwPosition const& rStartPos((**iter).GetMarkStart());
        // vector is sorted by start pos, so we can exit early
        if ( rStartPos > *pEnd )
            break;
        if ( *pStart <= rStartPos && rStartPos < *pEnd )
        {
            auto pOldMark = rDMA.findAnnotationBookmark((**iter).GetName());
            if ( pOldMark == rDMA.getBookmarksEnd() )
            {
                // at start of redlines use a 1-character length bookmark range
                // instead of a 0-character length bookmark position to avoid its losing
                sal_Int32 nLen = (*pStart == rStartPos) ? 1 : 0;
                SwPaM aPam( rStartPos.GetNode(), rStartPos.GetContentIndex(),
                                rStartPos.GetNode(), rStartPos.GetContentIndex() + nLen);
                ::sw::mark::Bookmark* pBookmark = rDMA.makeAnnotationBookmark(
                    aPam,
                    (**iter).GetName(),
                    sw::mark::InsertMode::New);
                if (pBookmark)
                {
                    pBookmark->SetKeyCode(vcl::KeyCode());
                    pBookmark->SetShortName(OUString());
                }
            }
        }
    }
}

void SwRangeRedline::MoveToSection()
{
    if( !m_oContentSect )
    {
        auto [pStart, pEnd] = StartEnd(); // SwPosition*

        SwDoc& rDoc = GetDoc();
        SwPaM aPam( *pStart, *pEnd );
        SwContentNode* pCSttNd = pStart->GetNode().GetContentNode();
        SwContentNode* pCEndNd = pEnd->GetNode().GetContentNode();

        if( !pCSttNd )
        {
            // In order to not move other Redlines' indices, we set them
            // to the end (is exclusive)
            const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
            for(SwRangeRedline* pRedl : rTable)
            {
                if( pRedl->GetBound() == *pStart )
                    pRedl->GetBound() = *pEnd;
                if( pRedl->GetBound(false) == *pStart )
                    pRedl->GetBound(false) = *pEnd;
            }
        }

        SwStartNode* pSttNd;
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=92 H=92 G=91

¤ Dauer der Verarbeitung: 0.11 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.