/* -*- 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 <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
#include <com/sun/star/document/XDocumentProperties.hpp>

#include <scitems.hxx>
#include <rangelst.hxx>
#include <editeng/flstitem.hxx>
#include <editeng/paperinf.hxx>
#include <editeng/sizeitem.hxx>
#include <o3tl/unit_conversion.hxx>
#include <officecfg/Office/Common.hxx>
#include <sal/log.hxx>
#include <sfx2/viewfrm.hxx>
#include <sfx2/app.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/printer.hxx>
#include <svl/numformat.hxx>
#include <svx/pageitem.hxx>
#include <svx/postattr.hxx>
#include <svx/svxids.hrc>
#include <unotools/configmgr.hxx>
#include <vcl/svapp.hxx>
#include <vcl/virdev.hxx>
#include <vcl/weld.hxx>
#include <osl/diagnose.h>

#include <docsh.hxx>
#include "docshimp.hxx"
#include <scmod.hxx>
#include <tabvwsh.hxx>
#include <viewdata.hxx>
#include <docpool.hxx>
#include <stlpool.hxx>
#include <patattr.hxx>
#include <uiitems.hxx>
#include <hints.hxx>
#include <docoptio.hxx>
#include <viewopti.hxx>
#include <pntlock.hxx>
#include <chgtrack.hxx>
#include <docfunc.hxx>
#include <formulacell.hxx>
#include <chgviset.hxx>
#include <progress.hxx>
#include <redcom.hxx>
#include <inputopt.hxx>
#include <drwlayer.hxx>
#include <inputhdl.hxx>
#include <conflictsdlg.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <markdata.hxx>
#include <memory>
#include <formulaopt.hxx>

#include <comphelper/lok.hxx>
#include <sfx2/lokhelper.hxx>

//          Redraw - Notifications

void ScDocShell::PostEditView( ScEditEngineDefaulter& rEditEngine, const ScAddress& rCursorPos )
{
//  Broadcast( ScEditViewHint( pEditEngine, rCursorPos ) );

        //  Test: only active ViewShell

    ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell();
    if (pViewSh && pViewSh->GetViewData().GetDocShell() == this)
    {
        ScEditViewHint aHint(rEditEngine, rCursorPos);
        pViewSh->Notify( *this, aHint );
    }
}

void ScDocShell::PostDataChanged()
{
    Broadcast( SfxHint( SfxHintId::ScDataChanged ) );
    SfxGetpApp()->Broadcast(SfxHint( SfxHintId::ScAnyDataChanged ));      // Navigator
    m_pDocument->PrepareFormulaCalc();
    //! notify navigator directly!
}

void ScDocShell::PostPaint( SCCOL nStartCol, SCROW nStartRow, SCTAB nStartTab,
                            SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab, PaintPartFlags nPart,
                            sal_uInt16 nExtFlags, tools::Long nMaxWidthAffectedHint )
{
    ScRange aRange(nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab);
    PostPaint(aRange, nPart, nExtFlags, nMaxWidthAffectedHint);
}

void ScDocShell::PostPaint( const ScRangeList& rRanges, PaintPartFlags nPart, sal_uInt16 nExtFlags, tools::Long nMaxWidthAffectedHint )
{
    ScRangeList aPaintRanges;
    std::set<SCTAB> aTabsInvalidated;
    const SCTAB nMaxTab = m_pDocument->GetTableCount() - 1;
    for (size_t i = 0, n = rRanges.size(); i < n; ++i)
    {
        const ScRange& rRange = rRanges[i];
        SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col();
        SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row();
        SCTAB nTab1 = rRange.aStart.Tab(), nTab2 = std::min<SCTAB>(nMaxTab, rRange.aEnd.Tab());

        if (nTab1 < 0 || nTab2 < 0)
            continue;

        if (!m_pDocument->ValidCol(nCol1))
        {
            nMaxWidthAffectedHint = -1; // Hint no longer valid
            nCol1 = m_pDocument->MaxCol();
        }
        if (!m_pDocument->ValidRow(nRow1)) nRow1 = m_pDocument->MaxRow();
        if (!m_pDocument->ValidCol(nCol2))
        {
            nMaxWidthAffectedHint = -1; // Hint no longer valid
            nCol2 = m_pDocument->MaxCol();
        }
        if (!m_pDocument->ValidRow(nRow2)) nRow2 = m_pDocument->MaxRow();

        if ( m_pPaintLockData )
        {
            // #i54081# PaintPartFlags::Extras still has to be broadcast because it changes the
            // current sheet if it's invalid. All other flags added to pPaintLockData.
            PaintPartFlags nLockPart = nPart & ~PaintPartFlags::Extras;
            if ( nLockPart != PaintPartFlags::NONE )
            {
                //! nExtFlags ???
                m_pPaintLockData->AddRange( ScRange( nCol1, nRow1, nTab1,
                                                   nCol2, nRow2, nTab2 ), nLockPart );
            }

            nPart &= PaintPartFlags::Extras;  // for broadcasting
            if (nPart == PaintPartFlags::NONE)
                continue;
        }

        if (nExtFlags & SC_PF_LINES)            // respect space for lines
        {
                                                //! check for hidden columns/rows!
            if (nCol1 > 0)
            {
                nMaxWidthAffectedHint = -1; // Hint no longer valid
                --nCol1;
            }
            if (nCol2 < m_pDocument->MaxCol())
            {
                nMaxWidthAffectedHint = -1; // Hint no longer valid
                ++nCol2;
            }
            if (nRow1>0) --nRow1;
            if (nRow2<m_pDocument->MaxRow()) ++nRow2;
        }

                                                // expand for the merged ones
        if (nExtFlags & SC_PF_TESTMERGE)
            m_pDocument->ExtendMerge( nCol1, nRow1, nCol2, nRow2, nTab1 );

        if ( nCol1 != 0 || nCol2 != m_pDocument->MaxCol() )
        {
            //  Extend to whole rows if SC_PF_WHOLEROWS is set, or rotated or non-left
            //  aligned cells are contained (see UpdatePaintExt).
            //  Special handling for RTL text (#i9731#) is unnecessary now with full
            //  support of right-aligned text.

            if ( ( nExtFlags & SC_PF_WHOLEROWS ) ||
                 m_pDocument->HasAttrib( nCol1,nRow1,nTab1,
                                      m_pDocument->MaxCol(),nRow2,nTab2, HasAttrFlags::Rotate | HasAttrFlags::RightOrCenter ) )
            {
                nCol1 = 0;
                nCol2 = m_pDocument->MaxCol();
                nMaxWidthAffectedHint = -1; // Hint no longer valid
            }
        }
        aPaintRanges.push_back(ScRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2));
        for (auto nTabNum = nTab1; nTabNum <= nTab2; ++nTabNum)
            aTabsInvalidated.insert(nTabNum);
    }

    Broadcast(ScPaintHint(aPaintRanges.Combine(), nPart, nMaxWidthAffectedHint));

    // LOK: we are supposed to update the row / columns headers (and actually
    // the document size too - cell size affects that, obviously)
    if ((nPart & (PaintPartFlags::Top | PaintPartFlags::Left)) && comphelper::LibreOfficeKit::isActive())
    {
        ScModelObj* pModel = GetModel();
        for (auto nTab : aTabsInvalidated)
            SfxLokHelper::notifyPartSizeChangedAllViews(pModel, nTab);
    }
}

void ScDocShell::PostPaintGridAll()
{
    PostPaint( 0,0,0, m_pDocument->MaxCol(),m_pDocument->MaxRow(),MAXTAB, PaintPartFlags::Grid );
}

void ScDocShell::PostPaintCell( SCCOL nCol, SCROW nRow, SCTAB nTab, tools::Long nMaxWidthAffectedHint )
{
    PostPaint( nCol,nRow,nTab, nCol,nRow,nTab, PaintPartFlags::Grid, SC_PF_TESTMERGE, nMaxWidthAffectedHint );
}

void ScDocShell::PostPaintCell( const ScAddress& rPos, tools::Long nMaxWidthAffectedHint )
{
    PostPaintCell( rPos.Col(), rPos.Row(), rPos.Tab(), nMaxWidthAffectedHint );
}

void ScDocShell::PostPaintExtras()
{
    PostPaint( 0,0,0, m_pDocument->MaxCol(),m_pDocument->MaxRow(),MAXTAB, PaintPartFlags::Extras );
}

void ScDocShell::UpdatePaintExt( sal_uInt16& rExtFlags, const ScRange& rRange )
{
    if ( ( rExtFlags & SC_PF_LINES ) == 0 &&
         m_pDocument->HasAttrib( rRange, HasAttrFlags::Lines | HasAttrFlags::Shadow | HasAttrFlags::Conditional ) )
    {
        //  If the range contains lines, shadow or conditional formats,
        //  set SC_PF_LINES to include one extra cell in all directions.

        rExtFlags |= SC_PF_LINES;
    }

    if ( ( rExtFlags & SC_PF_WHOLEROWS ) == 0 &&
         ( rRange.aStart.Col() != 0 || rRange.aEnd.Col() != m_pDocument->MaxCol() ) &&
         m_pDocument->HasAttrib( rRange, HasAttrFlags::Rotate | HasAttrFlags::RightOrCenter ) )
    {
        //  If the range contains (logically) right- or center-aligned cells,
        //  or rotated cells, set SC_PF_WHOLEROWS to paint the whole rows.
        //  This test isn't needed after the cell changes, because it's also
        //  tested in PostPaint. UpdatePaintExt may later be changed to do this
        //  only if called before the changes.

        rExtFlags |= SC_PF_WHOLEROWS;
    }
}

void ScDocShell::UpdatePaintExt( sal_uInt16& rExtFlags, SCCOL nStartCol, SCROW nStartRow, SCTAB nStartTab,
                                                   SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab )
{
    UpdatePaintExt( rExtFlags, ScRange( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab ) );
}

void ScDocShell::LockPaint_Impl(bool bDoc)
{
    if ( !m_pPaintLockData )
        m_pPaintLockData.reset( new ScPaintLockData );
    m_pPaintLockData->IncLevel(bDoc);
}

void ScDocShell::UnlockPaint_Impl(bool bDoc)
{
    if ( m_pPaintLockData )
    {
        if ( m_pPaintLockData->GetLevel(bDoc) )
            m_pPaintLockData->DecLevel(bDoc);
        if (!m_pPaintLockData->GetLevel(!bDoc) && !m_pPaintLockData->GetLevel(bDoc))
        {
            //     Execute Paint now

            // don't continue collecting
            std::unique_ptr<ScPaintLockData> pPaint = std::move(m_pPaintLockData);

            ScRangeListRef xRangeList = pPaint->GetRangeList();
            if ( xRangeList.is() )
            {
                PaintPartFlags nParts = pPaint->GetParts();
                for ( size_t i = 0, nCount = xRangeList->size(); i < nCount; i++ )
                {
                    //! nExtFlags ???
                    ScRange const & rRange = (*xRangeList)[i];
                    PostPaint( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(),
                                rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(),
                                nParts );
                }
            }

            if ( pPaint->GetModified() )
                SetDocumentModified();
        }
    }
    else
    {
        OSL_FAIL("UnlockPaint without LockPaint");
    }
}

void ScDocShell::LockDocument_Impl(sal_uInt16 nNew)
{
    if (!m_nDocumentLock)
    {
        ScDrawLayer* pDrawLayer = m_pDocument->GetDrawLayer();
        if (pDrawLayer)
            pDrawLayer->setLock(true);
    }
    m_nDocumentLock = nNew;
}

void ScDocShell::UnlockDocument_Impl(sal_uInt16 nNew)
{
    m_nDocumentLock = nNew;
    if (!m_nDocumentLock)
    {
        ScDrawLayer* pDrawLayer = m_pDocument->GetDrawLayer();
        if (pDrawLayer)
            pDrawLayer->setLock(false);
    }
}

void ScDocShell::SetLockCount(sal_uInt16 nNew)
{
    if (nNew)                   // set
    {
        if ( !m_pPaintLockData )
            m_pPaintLockData.reset( new ScPaintLockData );
        m_pPaintLockData->SetDocLevel(nNew-1);
        LockDocument_Impl(nNew);
    }
    else if (m_pPaintLockData)    // delete
    {
        m_pPaintLockData->SetDocLevel(0);  // at unlock, execute immediately
        UnlockPaint_Impl(true);                 // now
        UnlockDocument_Impl(0);
    }
}

void ScDocShell::LockPaint()
{
    LockPaint_Impl(false);
}

void ScDocShell::UnlockPaint()
{
    UnlockPaint_Impl(false);
}

void ScDocShell::LockDocument()
{
    LockPaint_Impl(true);
    LockDocument_Impl(m_nDocumentLock + 1);
}

void ScDocShell::UnlockDocument()
{
    if (m_nDocumentLock)
    {
        UnlockPaint_Impl(true);
        UnlockDocument_Impl(m_nDocumentLock - 1);
    }
    else
    {
        OSL_FAIL("UnlockDocument without LockDocument");
    }
}

void ScDocShell::SetInplace( bool bInplace )
{
    if (m_bIsInplace != bInplace)
    {
        m_bIsInplace = bInplace;
        CalcOutputFactor();
    }
}

void ScDocShell::CalcOutputFactor()
{
    if (m_bIsInplace)
    {
        m_nPrtToScreenFactor = 1.0;           // otherwise it does not match the inactive display
        return;
    }

    bool bTextWysiwyg = ScModule::get()->GetInputOptions().GetTextWysiwyg();
    if (bTextWysiwyg)
    {
        m_nPrtToScreenFactor = 1.0;
        return;
    }

    OUString aTestString(
            u"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789"_ustr);
    tools::Long nPrinterWidth = 0;
    const ScPatternAttr& rPattern(m_pDocument->getCellAttributeHelper().getDefaultCellAttribute());

    vcl::Font aDefFont;
    OutputDevice* pRefDev = GetRefDevice();
    MapMode aOldMode = pRefDev->GetMapMode();
    vcl::Font aOldFont = pRefDev->GetFont();

    pRefDev->SetMapMode(MapMode(MapUnit::MapPixel));
    rPattern.fillFontOnly(aDefFont, pRefDev); // font color doesn't matter here
    pRefDev->SetFont(aDefFont);
    nPrinterWidth = pRefDev->PixelToLogic(Size(pRefDev->GetTextWidth(aTestString), 0), MapMode(MapUnit::Map100thMM)).Width();
    pRefDev->SetFont(aOldFont);
    pRefDev->SetMapMode(aOldMode);

    ScopedVclPtrInstance< VirtualDevice > pVirtWindow( *Application::GetDefaultDevice() );
    pVirtWindow->SetMapMode(MapMode(MapUnit::MapPixel));
    rPattern.fillFontOnly(aDefFont, pVirtWindow); // font color doesn't matter here
    pVirtWindow->SetFont(aDefFont);
    double nWindowWidth = pVirtWindow->GetTextWidth(aTestString) / ScGlobal::nScreenPPTX;
    nWindowWidth = o3tl::convert(nWindowWidth, o3tl::Length::twip, o3tl::Length::mm100);

    if (nPrinterWidth && nWindowWidth)
        m_nPrtToScreenFactor = nPrinterWidth / nWindowWidth;
    else
    {
        OSL_FAIL("GetTextSize returns 0 ??");
        m_nPrtToScreenFactor = 1.0;
    }
}

void ScDocShell::InitOptions(bool bForLoading)      // called from InitNew and Load
{
    //  Settings from the SpellCheckCfg get into Doc- and ViewOptions

    LanguageType nDefLang, nCjkLang, nCtlLang;
    ScModule::GetSpellSettings( nDefLang, nCjkLang, nCtlLang );
    ScModule* pScMod = ScModule::get();

    ScDocOptions  aDocOpt  = pScMod->GetDocOptions();
    ScFormulaOptions aFormulaOpt = pScMod->GetFormulaOptions();
    ScViewOptions aViewOpt = pScMod->GetViewOptions();

    if (!comphelper::IsFuzzing())
    {
        // two-digit year entry from Tools->Options->General
        aDocOpt.SetYear2000(officecfg::Office::Common::DateFormat::TwoDigitYear::get());
    }

    if (bForLoading)
    {
        // #i112123# No style:decimal-places attribute means automatic decimals, not the configured default,
        // so it must not be taken from the global options.
        // Calculation settings are handled separately in ScXMLBodyContext::EndElement.
        aDocOpt.SetStdPrecision( SvNumberFormatter::UNLIMITED_PRECISION );

        // fdo#78294 The default null-date if
        // <table:null-date table:date-value='...' />
        // is absent is 1899-12-30 regardless what the configuration is set to.
        // Import filters may override this value.
        aDocOpt.SetDate( 30, 12, 1899);
    }

    m_pDocument->SetDocOptions( aDocOpt );
    m_pDocument->SetViewOptions( aViewOpt );
    SetFormulaOptions( aFormulaOpt, bForLoading );

    //  print options are now set directly before the printing

    m_pDocument->SetLanguage( nDefLang, nCjkLang, nCtlLang );
}

Printer* ScDocShell::GetDocumentPrinter()       // for OLE
{
    return m_pDocument->GetPrinter();
}

SfxPrinter* ScDocShell::GetPrinter(bool bCreateIfNotExist)
{
    return m_pDocument->GetPrinter(bCreateIfNotExist);
}

void ScDocShell::UpdateFontList()
{
    // pImpl->pFontList = new FontList( GetPrinter(), Application::GetDefaultDevice() );
    m_pImpl->pFontList.reset(new FontList(GetRefDevice(), nullptr));
    SvxFontListItem aFontListItem( m_pImpl->pFontList.get(), SID_ATTR_CHAR_FONTLIST );
    PutItem( aFontListItem );

    CalcOutputFactor();
}

OutputDevice* ScDocShell::GetRefDevice()
{
    return m_pDocument->GetRefDevice();
}

sal_uInt16 ScDocShell::SetPrinter( VclPtr<SfxPrinter> const & pNewPrinter, SfxPrinterChangeFlags nDiffFlags )
{
    SfxPrinter *pOld = m_pDocument->GetPrinter( false );
    if ( pOld && pOld->IsPrinting() )
        return SFX_PRINTERROR_BUSY;

    if (nDiffFlags & SfxPrinterChangeFlags::PRINTER)
    {
        if ( m_pDocument->GetPrinter() != pNewPrinter )
        {
            m_pDocument->SetPrinter( pNewPrinter );
            m_pDocument->SetPrintOptions();

            // MT: Use UpdateFontList: Will use Printer fonts only if needed!
            /*
            delete pImpl->pFontList;
            pImpl->pFontList = new FontList( pNewPrinter, Application::GetDefaultDevice() );
            SvxFontListItem aFontListItem( pImpl->pFontList, SID_ATTR_CHAR_FONTLIST );
            PutItem( aFontListItem );

            CalcOutputFactor();
            */
            ScModule* pScMod = ScModule::get();
            if (pScMod->GetInputOptions().GetTextWysiwyg())
                UpdateFontList();

            SfxViewFrame *pFrame = SfxViewFrame::GetFirst( this );
            while (pFrame)
            {
                SfxViewShell* pSh = pFrame->GetViewShell();
                if (ScTabViewShell* pViewSh = dynamic_cast<ScTabViewShell*>(pSh))
                {
                    ScInputHandler* pInputHdl = pScMod->GetInputHdl(pViewSh);
                    if (pInputHdl)
                        pInputHdl->UpdateRefDevice();
                }
                pFrame = SfxViewFrame::GetNext( *pFrame, this );
            }
        }
    }
    else if (nDiffFlags & SfxPrinterChangeFlags::JOBSETUP)
    {
        SfxPrinter* pOldPrinter = m_pDocument->GetPrinter();
        if (pOldPrinter)
        {
            pOldPrinter->SetJobSetup( pNewPrinter->GetJobSetup() );

            //  #i6706# Call SetPrinter with the old printer again, so the drawing layer
            //  RefDevice is set (calling ReformatAllTextObjects and rebuilding charts),
            //  because the JobSetup (printer device settings) may affect text layout.
            m_pDocument->SetPrinter( pOldPrinter );
            CalcOutputFactor();                         // also with the new settings
        }
    }

    if (nDiffFlags & SfxPrinterChangeFlags::OPTIONS)
    {
        m_pDocument->SetPrintOptions();        //! from new printer ???
    }

    if (nDiffFlags & (SfxPrinterChangeFlags::CHG_ORIENTATION | SfxPrinterChangeFlags::CHG_SIZE))
    {
        OUString aStyle = m_pDocument->GetPageStyle( GetCurTab() );
        ScStyleSheetPool* pStPl = m_pDocument->GetStyleSheetPool();
        SfxStyleSheet* pStyleSheet = static_cast<SfxStyleSheet*>(pStPl->Find(aStyle, SfxStyleFamily::Page));
        if (pStyleSheet)
        {
            SfxItemSet& rSet = pStyleSheet->GetItemSet();

            if (nDiffFlags & SfxPrinterChangeFlags::CHG_ORIENTATION)
            {
                const SvxPageItem& rOldItem = rSet.Get(ATTR_PAGE);
                bool bWasLand = rOldItem.IsLandscape();
                bool bNewLand = ( pNewPrinter->GetOrientation() == Orientation::Landscape );
                if (bNewLand != bWasLand)
                {
                    SvxPageItem aNewItem( rOldItem );
                    aNewItem.SetLandscape( bNewLand );
                    rSet.Put( aNewItem );

                    // flip size
                    Size aOldSize = rSet.Get(ATTR_PAGE_SIZE).GetSize();
                    // coverity[swapped_arguments : FALSE] - this is in the correct order
                    Size aNewSize(aOldSize.Height(),aOldSize.Width());
                    SvxSizeItem aNewSItem(ATTR_PAGE_SIZE,aNewSize);
                    rSet.Put( aNewSItem );
                }
            }
            if (nDiffFlags & SfxPrinterChangeFlags::CHG_SIZE)
            {
                SvxSizeItem aPaperSizeItem( ATTR_PAGE_SIZE, SvxPaperInfo::GetPaperSize(pNewPrinter) );
                rSet.Put( aPaperSizeItem );
            }
        }
    }

    PostPaint(0,0,0,m_pDocument->MaxCol(),m_pDocument->MaxRow(),MAXTAB,PaintPartFlags::All);

    return 0;
}

ScChangeAction* ScDocShell::GetChangeAction( const ScAddress& rPos )
{
    ScChangeTrack* pTrack = GetDocument().GetChangeTrack();
    if (!pTrack)
        return nullptr;

    SCTAB nTab = rPos.Tab();

    const ScChangeAction* pFound = nullptr;
    const ScChangeAction* pAction = pTrack->GetFirst();
    while (pAction)
    {
        ScChangeActionType eType = pAction->GetType();
        //! ScViewUtil::IsActionShown( *pAction, *pSettings, *pDoc )...
        if ( pAction->IsVisible() && eType != SC_CAT_DELETE_TABS )
        {
            const ScBigRange& rBig = pAction->GetBigRange();
            if ( rBig.aStart.Tab() == nTab )
            {
                ScRange aRange = rBig.MakeRange( GetDocument() );

                if ( eType == SC_CAT_DELETE_ROWS )
                    aRange.aEnd.SetRow( aRange.aStart.Row() );
                else if ( eType == SC_CAT_DELETE_COLS )
                    aRange.aEnd.SetCol( aRange.aStart.Col() );

                if ( aRange.Contains( rPos ) )
                {
                    pFound = pAction;       // the last one wins
                }
            }
            if ( pAction->GetType() == SC_CAT_MOVE )
            {
                ScRange aRange =
                    static_cast<const ScChangeActionMove*>(pAction)->
                    GetFromRange().MakeRange( GetDocument() );
                if ( aRange.Contains( rPos ) )
                {
                    pFound = pAction;
                }
            }
        }
        pAction = pAction->GetNext();
    }

    return const_cast<ScChangeAction*>(pFound);
}

void ScDocShell::SetChangeComment( ScChangeAction* pAction, const OUString& rComment )
{
    if (!pAction)
        return;

    pAction->SetComment( rComment );
    //! Undo ???
    SetDocumentModified();

    //  Dialog-Notify
    ScChangeTrack* pTrack = GetDocument().GetChangeTrack();
    if (pTrack)
    {
        sal_uLong nNumber = pAction->GetActionNumber();
        pTrack->NotifyModified( ScChangeTrackMsgType::Change, nNumber, nNumber );
    }
}

void ScDocShell::ExecuteChangeCommentDialog( ScChangeAction* pAction, weld::Window* pParent, bool bPrevNext)
{
    if (!pAction) return;           // without action is nothing...

    OUString aComment = pAction->GetComment();
    OUString aAuthor = pAction->GetUser();

    DateTime aDT = pAction->GetDateTime();
    OUString aDate = ScGlobal::getLocaleData().getDate( aDT ) + " " +
        ScGlobal::getLocaleData().getTime( aDT, false );

    SfxItemSetFixed<SID_ATTR_POSTIT_AUTHOR, SID_ATTR_POSTIT_TEXT> aSet( GetPool() );

    aSet.Put( SvxPostItTextItem  ( aComment, SID_ATTR_POSTIT_TEXT ) );
    aSet.Put( SvxPostItAuthorItem( aAuthor,  SID_ATTR_POSTIT_AUTHOR ) );
    aSet.Put( SvxPostItDateItem  ( aDate,    SID_ATTR_POSTIT_DATE ) );

    std::unique_ptr<ScRedComDialog> pDlg(new ScRedComDialog( pParent, aSet,this,pAction,bPrevNext));

    pDlg->Execute();
}

void ScDocShell::CompareDocument( ScDocument& rOtherDoc )
{
    ScChangeTrack* pTrack = m_pDocument->GetChangeTrack();
    if ( pTrack && pTrack->GetFirst() )
    {
        //! there are changes -> inquiry if needs to be deleted
    }

    m_pDocument->EndChangeTracking();
    m_pDocument->StartChangeTracking();

    OUString aOldUser;
    pTrack = m_pDocument->GetChangeTrack();
    if ( pTrack )
    {
        aOldUser = pTrack->GetUser();

        //  check if comparing to same document

        OUString aThisFile;
        const SfxMedium* pThisMed = GetMedium();
        if (pThisMed)
            aThisFile = pThisMed->GetName();
        OUString aOtherFile;
        ScDocShell* pOtherSh = rOtherDoc.GetDocumentShell();
        if (pOtherSh)
        {
            const SfxMedium* pOtherMed = pOtherSh->GetMedium();
            if (pOtherMed)
                aOtherFile = pOtherMed->GetName();
        }
        bool bSameDoc = ( aThisFile == aOtherFile && !aThisFile.isEmpty() );
        if ( !bSameDoc )
        {
            //  create change actions from comparing with the name of the user
            //  who last saved the document
            //  (only if comparing different documents)

            using namespace ::com::sun::star;
            uno::Reference<document::XDocumentProperties> xDocProps(
                GetModel()->getDocumentProperties());
            OSL_ENSURE(xDocProps.is(), "no DocumentProperties");
            OUString aDocUser = xDocProps->getModifiedBy();

            if ( !aDocUser.isEmpty() )
                pTrack->SetUser( aDocUser );
        }
    }

    m_pDocument->CompareDocument( rOtherDoc );

    pTrack = m_pDocument->GetChangeTrack();
    if ( pTrack )
        pTrack->SetUser( aOldUser );

    PostPaintGridAll();
    SetDocumentModified();
}

//              Merge (combine documents)

static bool lcl_Equal( const ScChangeAction* pA, const ScChangeAction* pB, bool bIgnore100Sec )
{
    return pA && pB &&
        pA->GetActionNumber() == pB->GetActionNumber() &&
        pA->GetType()         == pB->GetType() &&
        pA->GetUser()         == pB->GetUser() &&
        (bIgnore100Sec ?
         pA->GetDateTimeUTC().IsEqualIgnoreNanoSec( pB->GetDateTimeUTC() ) :
         pA->GetDateTimeUTC() == pB->GetDateTimeUTC());
    //  don't compare state if an old change has been accepted
}

static bool lcl_FindAction( ScDocument& rDoc, const ScChangeAction* pAction, ScDocument& rSearchDoc, const ScChangeAction* pFirstSearchAction, const ScChangeAction* pLastSearchAction, bool bIgnore100Sec )
{
    if ( !pAction || !pFirstSearchAction || !pLastSearchAction )
    {
        return false;
    }

    sal_uLong nLastSearchAction = pLastSearchAction->GetActionNumber();
    const ScChangeAction* pA = pFirstSearchAction;
    while ( pA && pA->GetActionNumber() <= nLastSearchAction )
    {
        if ( pAction->GetType() == pA->GetType() &&
             pAction->GetUser() == pA->GetUser() &&
             (bIgnore100Sec ?
                pAction->GetDateTimeUTC().IsEqualIgnoreNanoSec( pA->GetDateTimeUTC() ) :
                pAction->GetDateTimeUTC() == pA->GetDateTimeUTC() ) &&
             pAction->GetBigRange() == pA->GetBigRange() )
        {
            OUString aActionDesc = pAction->GetDescription(rDoc, true);
            OUString aADesc = pA->GetDescription(rSearchDoc, true);
            if (aActionDesc == aADesc)
            {
                OSL_FAIL( "lcl_FindAction(): found equal action!" );
                return true;
            }
        }
        pA = pA->GetNext();
    }

    return false;
}

void ScDocShell::MergeDocument( ScDocument& rOtherDoc, bool bShared, bool bCheckDuplicates, sal_uLong nOffset, ScChangeActionMergeMap* pMergeMap, bool bInverseMap )
{
    ScTabViewShell* pViewSh = GetBestViewShell( false );    //! functions to the DocShell
    if (!pViewSh)
        return;

    ScChangeTrack* pSourceTrack = rOtherDoc.GetChangeTrack();
    if (!pSourceTrack)
        return;             //! nothing to do - error notification?

    ScChangeTrack* pThisTrack = m_pDocument->GetChangeTrack();
    if ( !pThisTrack )
    {   // turn on
        m_pDocument->StartChangeTracking();
        pThisTrack = m_pDocument->GetChangeTrack();
        OSL_ENSURE(pThisTrack,"ChangeTracking not enabled?");
        if ( !bShared )
        {
            // turn on visual RedLining
            ScChangeViewSettings aChangeViewSet;
            aChangeViewSet.SetShowChanges(true);
            m_pDocument->SetChangeViewSettings(aChangeViewSet);
        }
    }

    // include Nano seconds in compare?
    bool bIgnore100Sec = !pSourceTrack->IsTimeNanoSeconds() ||
            !pThisTrack->IsTimeNanoSeconds();

    //  find common initial position
    sal_uLong nFirstNewNumber = 0;
    const ScChangeAction* pSourceAction = pSourceTrack->GetFirst();
    const ScChangeAction* pThisAction = pThisTrack->GetFirst();
    // skip identical actions
    while ( lcl_Equal( pSourceAction, pThisAction, bIgnore100Sec ) )
    {
        nFirstNewNumber = pSourceAction->GetActionNumber() + 1;
        pSourceAction = pSourceAction->GetNext();
        pThisAction = pThisAction->GetNext();
    }
    //  pSourceAction and pThisAction now point to the first "own" actions
    //  The common actions before don't interest at all

    //! Inquiry if the documents where equal before the change tracking !!!

    const ScChangeAction* pFirstMergeAction = pSourceAction;
    const ScChangeAction* pFirstSearchAction = pThisAction;

    // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong
    const ScChangeAction* pLastSearchAction = pThisTrack->GetLast();

    //  Create MergeChangeData from the following actions
    sal_uLong nNewActionCount = 0;
    const ScChangeAction* pCount = pSourceAction;
    while ( pCount )
    {
        if ( bShared || !ScChangeTrack::MergeIgnore( *pCount, nFirstNewNumber ) )
            ++nNewActionCount;
        pCount = pCount->GetNext();
    }
    if (!nNewActionCount)
        return;             //! nothing to do - error notification?
                            //  from here on no return

    ScProgress aProgress( this, u"..."_ustr, nNewActionCount, true );

    sal_uLong nLastMergeAction = pSourceTrack->GetLast()->GetActionNumber();
    // UpdateReference-Undo, valid references for the last common state
    pSourceTrack->MergePrepare( pFirstMergeAction, bShared );

    //  adjust MergeChangeData to all yet following actions in this document
    //  -> references valid for this document
    while ( pThisAction )
    {
        // #i87049# [Collaboration] Conflict between delete row and insert content is not merged correctly
        if ( !bShared || !ScChangeTrack::MergeIgnore( *pThisAction, nFirstNewNumber ) )
        {
            ScChangeActionType eType = pThisAction->GetType();
            switch ( eType )
            {
                case SC_CAT_INSERT_COLS :
                case SC_CAT_INSERT_ROWS :
                case SC_CAT_INSERT_TABS :
                    pSourceTrack->AppendInsert( pThisAction->GetBigRange().MakeRange( GetDocument() ) );
                break;
                case SC_CAT_DELETE_COLS :
                case SC_CAT_DELETE_ROWS :
                case SC_CAT_DELETE_TABS :
                {
                    const ScChangeActionDel* pDel = static_cast<const ScChangeActionDel*>(pThisAction);
                    if ( pDel->IsTopDelete() && !pDel->IsTabDeleteCol() )
                    {   // deleted table contains deleted cols, which are not
                        sal_uLong nStart, nEnd;
                        pSourceTrack->AppendDeleteRange(
                            pDel->GetOverAllRange().MakeRange( GetDocument() ), nullptr, nStart, nEnd );
                    }
                }
                break;
                case SC_CAT_MOVE :
                {
                    const ScChangeActionMove* pMove = static_cast<const ScChangeActionMove*>(pThisAction);
                    pSourceTrack->AppendMove( pMove->GetFromRange().MakeRange( GetDocument() ),
                        pMove->GetBigRange().MakeRange( GetDocument() ), nullptr );
                }
                break;
                default:
                {
                    // added to avoid warnings
                }
            }
        }
        pThisAction = pThisAction->GetNext();
    }

    LockPaint();    // #i73877# no repainting after each action

    //  take over MergeChangeData into the current document
    bool bHasRejected = false;
    OUString aOldUser = pThisTrack->GetUser();
    pThisTrack->SetUseFixDateTime( true );
    ScMarkData& rMarkData = pViewSh->GetViewData().GetMarkData();
    ScMarkData aOldMarkData( rMarkData );
    pSourceAction = pFirstMergeAction;
    while ( pSourceAction && pSourceAction->GetActionNumber() <= nLastMergeAction )
    {
        bool bMergeAction = false;
        if ( bShared )
        {
            if ( !bCheckDuplicates || !lcl_FindAction( rOtherDoc, pSourceAction, *m_pDocument, pFirstSearchAction, pLastSearchAction, bIgnore100Sec ) )
            {
                bMergeAction = true;
            }
        }
        else
        {
            if ( !ScChangeTrack::MergeIgnore( *pSourceAction, nFirstNewNumber ) )
            {
                bMergeAction = true;
            }
        }

        if ( bMergeAction )
        {
            ScChangeActionType eSourceType = pSourceAction->GetType();
            if ( !bShared && pSourceAction->IsDeletedIn() )
            {
                //! does it need to be determined yet if really deleted in
                //! _this_ document?

                //  lies in a range, which was deleted in this document
                //  -> is omitted
                //! ??? revert deletion action ???
                //! ??? save action somewhere else  ???
#if OSL_DEBUG_LEVEL > 0
                OUString aValue;
                if ( eSourceType == SC_CAT_CONTENT )
                    aValue = static_cast<const ScChangeActionContent*>(pSourceAction)->GetNewString( *m_pDocument );
                SAL_WARN( "sc", aValue << " omitted");
#endif
            }
            else
            {
                //! Take over date/author/comment of the source action!

                pThisTrack->SetUser( pSourceAction->GetUser() );
                pThisTrack->SetFixDateTimeUTC( pSourceAction->GetDateTimeUTC() );
                sal_uLong nOldActionMax = pThisTrack->GetActionMax();

                bool bExecute = true;
                sal_uLong nReject = pSourceAction->GetRejectAction();
                if ( nReject )
                {
                    if ( bShared )
                    {
                        if ( nReject >= nFirstNewNumber )
                        {
                            nReject += nOffset;
                        }
                        ScChangeAction* pOldAction = pThisTrack->GetAction( nReject );
                        if ( pOldAction && pOldAction->IsVirgin() )
                        {
                            pThisTrack->Reject( pOldAction );
                            bHasRejected = true;
                            bExecute = false;
                        }
                    }
                    else
                    {
                        //  decline old action (of the common ones)
                        ScChangeAction* pOldAction = pThisTrack->GetAction( nReject );
                        if (pOldAction && pOldAction->GetState() == SC_CAS_VIRGIN)
                        {
                            //! what happens at actions, which were accepted in this document???
                            //! error notification or what???
                            //! or execute reject change normally

                            pThisTrack->Reject(pOldAction);
                            bHasRejected = true;                // for Paint
                        }
                        bExecute = false;
                    }
                }

                if ( bExecute )
                {
                    //  execute normally
                    ScRange aSourceRange = pSourceAction->GetBigRange().MakeRange( GetDocument() );
                    rMarkData.SelectOneTable( aSourceRange.aStart.Tab() );
                    switch ( eSourceType )
                    {
                        case SC_CAT_CONTENT:
                        {
                            //! Test if it was at the very bottom in the document, then automatic
                            //! row insert ???

                            OSL_ENSURE( aSourceRange.aStart == aSourceRange.aEnd, "huch?" );
                            ScAddress aPos = aSourceRange.aStart;
                            OUString aValue = static_cast<const ScChangeActionContent*>(pSourceAction)->GetNewString( *m_pDocument );
                            ScMatrixMode eMatrix = ScMatrixMode::NONE;
                            const ScCellValue& rCell = static_cast<const ScChangeActionContent*>(pSourceAction)->GetNewCell();
                            if (rCell.getType() == CELLTYPE_FORMULA)
                                eMatrix = rCell.getFormula()->GetMatrixFlag();
                            switch ( eMatrix )
                            {
                                case ScMatrixMode::NONE :
                                    pViewSh->EnterData( aPos.Col(), aPos.Row(), aPos.Tab(), aValue );
                                break;
                                case ScMatrixMode::Formula :
                                {
                                    SCCOL nCols;
                                    SCROW nRows;
                                    rCell.getFormula()->GetMatColsRows(nCols, nRows);
                                    aSourceRange.aEnd.SetCol( aPos.Col() + nCols - 1 );
                                    aSourceRange.aEnd.SetRow( aPos.Row() + nRows - 1 );
                                    aValue = aValue.copy(1, aValue.getLength()-2); // remove the 1st and last characters.
                                    GetDocFunc().EnterMatrix( aSourceRange,
                                        nullptr, nullptr, aValue, false, false,
                                        OUString(), formula::FormulaGrammar::GRAM_DEFAULT );
                                }
                                break;
                                case ScMatrixMode::Reference :     // do nothing
                                break;
                            }
                        }
                        break;
                        case SC_CAT_INSERT_TABS :
                        {
                            OUString aName;
                            m_pDocument->CreateValidTabName( aName );
                            (void)GetDocFunc().InsertTable( aSourceRange.aStart.Tab(), aName, true, false );
                        }
                        break;
                        case SC_CAT_INSERT_ROWS:
                            (void)GetDocFunc().InsertCells( aSourceRange, nullptr, INS_INSROWS_BEFORE, true, false );
                        break;
                        case SC_CAT_INSERT_COLS:
                            (void)GetDocFunc().InsertCells( aSourceRange, nullptr, INS_INSCOLS_BEFORE, true, false );
                        break;
                        case SC_CAT_DELETE_TABS :
                            (void)GetDocFunc().DeleteTable( aSourceRange.aStart.Tab(), true );
                        break;
                        case SC_CAT_DELETE_ROWS:
                        {
                            const ScChangeActionDel* pDel = static_cast<const ScChangeActionDel*>(pSourceAction);
                            if ( pDel->IsTopDelete() )
                            {
                                aSourceRange = pDel->GetOverAllRange().MakeRange( GetDocument() );
                                (void)GetDocFunc().DeleteCells( aSourceRange, nullptr, DelCellCmd::Rows, false );

                                // #i101099# [Collaboration] Changes are not correctly shown
                                if ( bShared )
                                {
                                    ScChangeAction* pAct = pThisTrack->GetLast();
                                    if ( pAct && pAct->GetType() == eSourceType && pAct->IsDeletedIn() && !pSourceAction->IsDeletedIn() )
                                    {
                                        pAct->RemoveAllDeletedIn();
                                    }
                                }
                            }
                        }
                        break;
                        case SC_CAT_DELETE_COLS:
                        {
                            const ScChangeActionDel* pDel = static_cast<const ScChangeActionDel*>(pSourceAction);
                            if ( pDel->IsTopDelete() && !pDel->IsTabDeleteCol() )
                            {   // deleted table contains deleted cols, which are not
                                aSourceRange = pDel->GetOverAllRange().MakeRange( GetDocument() );
                                (void)GetDocFunc().DeleteCells( aSourceRange, nullptr, DelCellCmd::Cols, false );
                            }
                        }
                        break;
                        case SC_CAT_MOVE :
                        {
                            const ScChangeActionMove* pMove = static_cast<const ScChangeActionMove*>(pSourceAction);
                            ScRange aFromRange( pMove->GetFromRange().MakeRange( GetDocument() ) );
                            (void)GetDocFunc().MoveBlock( aFromRange,
                                aSourceRange.aStart, true, true, false, false );
                        }
                        break;
                        default:
                        {
                            // added to avoid warnings
                        }
                    }
                }
                const OUString& rComment = pSourceAction->GetComment();
                if ( !rComment.isEmpty() )
                {
                    ScChangeAction* pAct = pThisTrack->GetLast();
                    if ( pAct && pAct->GetActionNumber() > nOldActionMax )
                        pAct->SetComment( rComment );
                    else
                        OSL_FAIL( "MergeDocument: what to do with the comment?!?" );
                }

                // adjust references
                pSourceTrack->MergeOwn( const_cast<ScChangeAction*>(pSourceAction), nFirstNewNumber, bShared );

                // merge action state
                if ( bShared && !pSourceAction->IsRejected() )
                {
                    ScChangeAction* pAct = pThisTrack->GetLast();
                    if ( pAct && pAct->GetActionNumber() > nOldActionMax )
                    {
                        ScChangeTrack::MergeActionState( pAct, pSourceAction );
                    }
                }

                // fill merge map
                if ( bShared && pMergeMap )
                {
                    ScChangeAction* pAct = pThisTrack->GetLast();
                    if ( pAct && pAct->GetActionNumber() > nOldActionMax )
                    {
                        sal_uLong nActionMax = pAct->GetActionNumber();
                        sal_uLong nActionCount = nActionMax - nOldActionMax;
                        sal_uLong nAction = nActionMax - nActionCount + 1;
                        sal_uLong nSourceAction = pSourceAction->GetActionNumber() - nActionCount + 1;
                        while ( nAction <= nActionMax )
                        {
                            if ( bInverseMap )
                            {
                                (*pMergeMap)[ nAction++ ] = nSourceAction++;
                            }
                            else
                            {
                                (*pMergeMap)[ nSourceAction++ ] = nAction++;
                            }
                        }
                    }
                }
            }
            aProgress.SetStateCountDown( --nNewActionCount );
        }
        pSourceAction = pSourceAction->GetNext();
    }

    rMarkData = std::move(aOldMarkData);
    pThisTrack->SetUser(aOldUser);
    pThisTrack->SetUseFixDateTime( false );

    pSourceTrack->Clear();      //! this one is bungled now

    if (bHasRejected)
        PostPaintGridAll();         // Reject() doesn't paint itself

    UnlockPaint();
}

bool ScDocShell::MergeSharedDocument( ScDocShell* pSharedDocShell )
{
    if ( !pSharedDocShell )
    {
        return false;
    }

    ScChangeTrack* pThisTrack = m_pDocument->GetChangeTrack();
    if ( !pThisTrack )
    {
        return false;
    }

    ScDocument& rSharedDoc = pSharedDocShell->GetDocument();
    ScChangeTrack* pSharedTrack = rSharedDoc.GetChangeTrack();
    if ( !pSharedTrack )
    {
        return false;
    }

    // reset show changes
    ScChangeViewSettings aChangeViewSet;
    aChangeViewSet.SetShowChanges( false );
    m_pDocument->SetChangeViewSettings( aChangeViewSet );

    // find first merge action in this document
    bool bIgnore100Sec = !pThisTrack->IsTimeNanoSeconds() || !pSharedTrack->IsTimeNanoSeconds();
    ScChangeAction* pThisAction = pThisTrack->GetFirst();
    ScChangeAction* pSharedAction = pSharedTrack->GetFirst();
    while ( lcl_Equal( pThisAction, pSharedAction, bIgnore100Sec ) )
    {
        pThisAction = pThisAction->GetNext();
        pSharedAction = pSharedAction->GetNext();
    }

    if ( pSharedAction )
    {
        if ( pThisAction )
        {
            // merge own changes into shared document
            sal_uLong nActStartShared = pSharedAction->GetActionNumber();
            sal_uLong nActEndShared = pSharedTrack->GetActionMax();
            std::optional<ScDocument> pTmpDoc(std::in_place);
            for ( sal_Int32 nIndex = 0; nIndex < m_pDocument->GetTableCount(); ++nIndex )
            {
                OUString sTabName;
                pTmpDoc->CreateValidTabName( sTabName );
                pTmpDoc->InsertTab( SC_TAB_APPEND, sTabName );
            }
            m_pDocument->GetChangeTrack()->Clone( *pTmpDoc );
            ScChangeActionMergeMap aOwnInverseMergeMap;
            pSharedDocShell->MergeDocument( *pTmpDoc, true, true, 0, &aOwnInverseMergeMap, true );
            pTmpDoc.reset();
            sal_uLong nActStartOwn = nActEndShared + 1;
            sal_uLong nActEndOwn = pSharedTrack->GetActionMax();

            // find conflicts
            ScConflictsList aConflictsList;
            ScConflictsFinder aFinder( pSharedTrack, nActStartShared, nActEndShared, nActStartOwn, nActEndOwn, aConflictsList );
            if ( aFinder.Find() )
            {
                ScConflictsListHelper::TransformConflictsList( aConflictsList, nullptr, &aOwnInverseMergeMap );
                if (ScViewData* pViewData = GetViewData())
                {
                    bool bLoop = true;
                    while ( bLoop )
                    {
                        bLoop = false;
                        weld::Window* pWin = GetActiveDialogParent();
                        ScConflictsDlg aDlg(pWin, *pViewData, rSharedDoc, aConflictsList);
                        if (aDlg.run() == RET_CANCEL)
                        {
                            std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(pWin,
                                                                           VclMessageType::Question, VclButtonsType::YesNo,
                                                                           ScResId(STR_DOC_WILLNOTBESAVED)));
                            xQueryBox->set_default_response(RET_YES);
                            if (xQueryBox->run() == RET_YES)
                            {
                                return false;
                            }
                            else
                            {
                                bLoop = true;
                            }
                        }
                    }
                }
            }

            // undo own changes in shared document
            pSharedTrack->Undo( nActStartOwn, nActEndOwn );

            // clone change track for merging into own document
            pTmpDoc.emplace();
            for ( sal_Int32 nIndex = 0; nIndex < m_pDocument->GetTableCount(); ++nIndex )
            {
                OUString sTabName;
                pTmpDoc->CreateValidTabName( sTabName );
                pTmpDoc->InsertTab( SC_TAB_APPEND, sTabName );
            }
            pThisTrack->Clone( *pTmpDoc );

            // undo own changes since last save in own document
            sal_uLong nStartShared = pThisAction->GetActionNumber();
            ScChangeAction* pAction = pThisTrack->GetLast();
            while ( pAction && pAction->GetActionNumber() >= nStartShared )
            {
                pThisTrack->Reject( pAction, true );
                pAction = pAction->GetPrev();
            }

            // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong
            pThisTrack->Undo( nStartShared, pThisTrack->GetActionMax(), true );

            // merge shared changes into own document
            ScChangeActionMergeMap aSharedMergeMap;
            MergeDocument( rSharedDoc, true, true, 0, &aSharedMergeMap );
            sal_uLong nEndShared = pThisTrack->GetActionMax();

            // resolve conflicts for shared non-content actions
            if ( !aConflictsList.empty() )
            {
                ScConflictsListHelper::TransformConflictsList( aConflictsList, &aSharedMergeMap, nullptr );
                ScConflictsResolver aResolver( pThisTrack, aConflictsList );
                pAction = pThisTrack->GetAction( nEndShared );
                while ( pAction && pAction->GetActionNumber() >= nStartShared )
                {
                    aResolver.HandleAction( pAction, true /*bIsSharedAction*/,
                        false /*bHandleContentAction*/, true /*bHandleNonContentAction*/ );
                    pAction = pAction->GetPrev();
                }
            }
            nEndShared = pThisTrack->GetActionMax();

            // only show changes from shared document
            aChangeViewSet.SetShowChanges( true );
            aChangeViewSet.SetShowAccepted( true );
            aChangeViewSet.SetHasActionRange();
            aChangeViewSet.SetTheActionRange( nStartShared, nEndShared );
            m_pDocument->SetChangeViewSettings( aChangeViewSet );

            // merge own changes back into own document
            sal_uLong nStartOwn = nEndShared + 1;
            ScChangeActionMergeMap aOwnMergeMap;
            MergeDocument( *pTmpDoc, true, true, nEndShared - nStartShared + 1, &aOwnMergeMap );
            pTmpDoc.reset();
            sal_uLong nEndOwn = pThisTrack->GetActionMax();

            // resolve conflicts for shared content actions and own actions
            if ( !aConflictsList.empty() )
            {
                ScConflictsListHelper::TransformConflictsList( aConflictsList, nullptr, &aOwnMergeMap );
                ScConflictsResolver aResolver( pThisTrack, aConflictsList );
                pAction = pThisTrack->GetAction( nEndShared );
                while ( pAction && pAction->GetActionNumber() >= nStartShared )
                {
                    aResolver.HandleAction( pAction, true /*bIsSharedAction*/,
                        true /*bHandleContentAction*/, false /*bHandleNonContentAction*/ );
                    pAction = pAction->GetPrev();
                }

                pAction = pThisTrack->GetAction( nEndOwn );
                while ( pAction && pAction->GetActionNumber() >= nStartOwn )
                {
                    aResolver.HandleAction( pAction, false /*bIsSharedAction*/,
                        true /*bHandleContentAction*/, true /*bHandleNonContentAction*/ );
                    pAction = pAction->GetPrev();
                }
            }
        }
        else
        {
            // merge shared changes into own document
            sal_uLong nStartShared = pThisTrack->GetActionMax() + 1;
            MergeDocument( rSharedDoc, true, true );
            sal_uLong nEndShared = pThisTrack->GetActionMax();

            // only show changes from shared document
            aChangeViewSet.SetShowChanges( true );
            aChangeViewSet.SetShowAccepted( true );
            aChangeViewSet.SetHasActionRange();
            aChangeViewSet.SetTheActionRange( nStartShared, nEndShared );
            m_pDocument->SetChangeViewSettings( aChangeViewSet );
        }

        // update view
        PostPaintExtras();
        PostPaintGridAll();

        std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(GetActiveDialogParent(),
                                                      VclMessageType::Info, VclButtonsType::Ok,
                                                      ScResId(STR_DOC_UPDATED)));
        xInfoBox->run();
    }

    return ( pThisAction != nullptr );
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
