/* -*- 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 <scitems.hxx>
#include <svx/svdpage.hxx>
#include <sfx2/docfile.hxx>
#include <comphelper/classids.hxx>
#include <comphelper/lok.hxx>
#include <sot/formats.hxx>
#include <sot/storage.hxx>
#include <vcl/graph.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <tools/urlobj.hxx>
#include <sot/exchange.hxx>
#include <memory>
#include <vcl/uitest/logger.hxx>
#include <vcl/uitest/eventdescription.hxx>
#include <vcl/TypeSerializer.hxx>
#include <osl/diagnose.h>

#include <attrib.hxx>
#include <patattr.hxx>
#include <dociter.hxx>
#include <viewfunc.hxx>
#include <tabvwsh.hxx>
#include <docsh.hxx>
#include <docfunc.hxx>
#include <undoblk.hxx>
#include <refundo.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <global.hxx>
#include <transobj.hxx>
#include <drwtrans.hxx>
#include <chgtrack.hxx>
#include <waitoff.hxx>
#include <scmod.hxx>
#include <inputopt.hxx>
#include <warnbox.hxx>
#include <drwlayer.hxx>
#include <editable.hxx>
#include <docuno.hxx>
#include <clipparam.hxx>
#include <undodat.hxx>
#include <drawview.hxx>
#include <cliputil.hxx>
#include <clipoptions.hxx>
#include <gridwin.hxx>
#include <SheetViewManager.hxx>
#include <uiitems.hxx>
#include <com/sun/star/util/XCloneable.hpp>
#include <sfx2/lokhelper.hxx>
#include <sc.hrc>
#include <sfx2/bindings.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>

using namespace com::sun::star;

namespace {

void collectUIInformation(std::map<OUString, OUString>&& aParameters, const OUString& action)
{
    EventDescription aDescription;
    aDescription.aID = "grid_window";
    aDescription.aAction = action;
    aDescription.aParameters = std::move(aParameters);
    aDescription.aParent = "MainWindow";
    aDescription.aKeyWord = "ScGridWinUIObject";

    UITestLogger::getInstance().logEvent(aDescription);
}

}

//  GlobalName of writer-DocShell from comphelper/classids.hxx

//      C U T

void ScViewFunc::CutToClip()
{
    UpdateInputLine();

    ScEditableTester aTester = ScEditableTester::CreateAndTestView(this);
    if (!aTester.IsEditable())                  // selection editable?
    {
        ErrorMessage( aTester.GetMessageId() );
        return;
    }

    ScRange aRange;                             // delete this range
    if ( GetViewData().GetSimpleArea( aRange ) == SC_MARK_SIMPLE )
    {
        ScDocument& rDoc = GetViewData().GetDocument();
        ScDocShell* pDocSh = GetViewData().GetDocShell();
        ScMarkData& rMark = GetViewData().GetMarkData();
        const bool bRecord(rDoc.IsUndoEnabled());                  // Undo/Redo

        ScDocShellModificator aModificator( *pDocSh );

        if ( !rMark.IsMarked() && !rMark.IsMultiMarked() )          // mark the range if not marked yet
        {
            DoneBlockMode();
            InitOwnBlockMode( aRange );
            rMark.SetMarkArea( aRange );
            MarkDataChanged();
        }

        CopyToClip( nullptr, true, false, true/*bIncludeObjects*/ );           // copy to clipboard

        ScAddress aOldEnd( aRange.aEnd );       //  combined cells in this range?
        rDoc.ExtendMerge( aRange, true );

        ScDocumentUniquePtr pUndoDoc;
        if ( bRecord )
        {
            pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
            pUndoDoc->InitUndoSelected( rDoc, rMark );
            // all sheets - CopyToDocument skips those that don't exist in pUndoDoc
            ScRange aCopyRange = aRange;
            aCopyRange.aStart.SetTab(0);
            aCopyRange.aEnd.SetTab(rDoc.GetTableCount()-1);
            rDoc.CopyToDocument( aCopyRange, (InsertDeleteFlags::ALL & ~InsertDeleteFlags::OBJECTS) | InsertDeleteFlags::NOCAPTIONS, false, *pUndoDoc );
            rDoc.BeginDrawUndo();
        }

        sal_uInt16 nExtFlags = 0;
        pDocSh->UpdatePaintExt( nExtFlags, aRange );

        rMark.MarkToMulti();
        rDoc.DeleteSelection( InsertDeleteFlags::ALL, rMark );
        rDoc.DeleteObjectsInSelection( rMark );
        rMark.MarkToSimple();

        if ( !AdjustRowHeight( aRange.aStart.Row(), aRange.aEnd.Row(), true ) )
            pDocSh->PostPaint( aRange, PaintPartFlags::Grid, nExtFlags );

        if ( bRecord )                          // Draw-Undo now available
            pDocSh->GetUndoManager()->AddUndoAction(
                std::make_unique<ScUndoCut>( *pDocSh, aRange, aOldEnd, rMark, std::move(pUndoDoc) ) );

        aModificator.SetDocumentModified();
        pDocSh->UpdateOle(GetViewData());

        CellContentChanged();

        OUString aStartAddress =  aRange.aStart.GetColRowString();
        OUString aEndAddress = aRange.aEnd.GetColRowString();

        collectUIInformation({{"RANGE", aStartAddress + ":" + aEndAddress}}, u"CUT"_ustr);
    }
    else
        ErrorMessage( STR_NOMULTISELECT );
}

//      C O P Y

bool ScViewFunc::CopyToClip( ScDocument* pClipDoc, bool bCut, bool bApi, bool bIncludeObjects, bool bStopEdit )
{
    ScRange aRange;
    ScMarkType eMarkType = GetViewData().GetSimpleArea( aRange );
    ScMarkData& rMark = GetViewData().GetMarkData();
    bool bDone = false;

    if ( eMarkType == SC_MARK_SIMPLE || eMarkType == SC_MARK_SIMPLE_FILTERED )
    {
       ScRangeList aRangeList( aRange );
       bDone = CopyToClip( pClipDoc, aRangeList, bCut, bApi, bIncludeObjects, bStopEdit );
    }
    else if (eMarkType == SC_MARK_MULTI)
    {
        ScRangeList aRangeList;
        rMark.MarkToSimple();
        rMark.FillRangeListWithMarks(&aRangeList, false);
        bDone = CopyToClip( pClipDoc, aRangeList, bCut, bApi, bIncludeObjects, bStopEdit );
    }
    else
    {
        if (!bApi)
            ErrorMessage(STR_NOMULTISELECT);
    }
    if( !bCut ){
        OUString aStartAddress =  aRange.aStart.GetColRowString();
        OUString aEndAddress = aRange.aEnd.GetColRowString();
        collectUIInformation({{"RANGE", aStartAddress + ":" + aEndAddress}}, u"COPY"_ustr);
    }
    return bDone;
}

// Copy the content of the Range into clipboard.
bool ScViewFunc::CopyToClip( ScDocument* pClipDoc, const ScRangeList& rRanges, bool bCut, bool bApi, bool bIncludeObjects, bool bStopEdit )
{
    if ( rRanges.empty() )
        return false;
    if ( bStopEdit )
        UpdateInputLine();

    bool bDone;
    if (rRanges.size() > 1) // isMultiRange
        bDone = CopyToClipMultiRange(pClipDoc, rRanges, bCut, bApi, bIncludeObjects);
    else
        bDone = CopyToClipSingleRange(pClipDoc, rRanges, bCut, bIncludeObjects);

    return bDone;
}

bool ScViewFunc::CopyToClipSingleRange( ScDocument* pClipDoc, const ScRangeList& rRanges, bool bCut, bool bIncludeObjects )
{
    ScRange aRange = rRanges[0];
    ScClipParam aClipParam( aRange, bCut );
    aClipParam.maRanges = rRanges;
    ScDocument& rDoc = GetViewData().GetDocument();
    ScMarkData& rMark = GetViewData().GetMarkData();

    if (rDoc.HasSelectedBlockMatrixFragment( aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row(), rMark ) )
        return false;

    std::shared_ptr<ScDocument> pSysClipDoc;
    if ( !pClipDoc )                                    // no clip doc specified
    {
        // Create one (deleted by ScTransferObj), and copy into system.
        pSysClipDoc = std::make_shared<ScDocument>( SCDOCMODE_CLIP );
        pClipDoc = pSysClipDoc.get();
    }
    if ( !bCut )
    {
        ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
        if ( pChangeTrack )
            pChangeTrack->ResetLastCut();
    }

    if ( pSysClipDoc && bIncludeObjects )
    {
        bool bAnyOle = rDoc.HasOLEObjectsInArea( aRange );
        // Update ScGlobal::xDrawClipDocShellRef.
        ScDrawLayer::SetGlobalDrawPersist( ScTransferObj::SetDrawClipDoc( bAnyOle, pSysClipDoc ) );
    }

    // is this necessary?, will setting the doc id upset the
    // following paste operation with range? would be nicer to just set this always
    // and lose the 'if' above
    aClipParam.setSourceDocID( rDoc.GetDocumentID() );

    if (ScDocShell* pObjectShell = rDoc.GetDocumentShell())
    {
        // Copy document properties from pObjectShell to pClipDoc (to its clip options, as it has no object shell).
        uno::Reference<util::XCloneable> xCloneable(pObjectShell->getDocProperties(), uno::UNO_QUERY_THROW);
        std::unique_ptr<ScClipOptions> pOptions(new ScClipOptions);
        pOptions->m_xDocumentProperties.set(xCloneable->createClone(), uno::UNO_QUERY);
        pClipDoc->SetClipOptions(std::move(pOptions));
    }

    rDoc.CopyToClip( aClipParam, pClipDoc, &rMark, false, bIncludeObjects );
    if (ScDrawLayer* pDrawLayer = pClipDoc->GetDrawLayer())
    {
        ScClipParam& rClipDocClipParam = pClipDoc->GetClipParam();
        ScRangeListVector& rRangesVector = rClipDocClipParam.maProtectedChartRangesVector;
        SCTAB nTabCount = pClipDoc->GetTableCount();
        for ( SCTAB nTab = 0; nTab < nTabCount; ++nTab )
        {
            SdrPage* pPage = pDrawLayer->GetPage( static_cast< sal_uInt16 >( nTab ) );
            if ( pPage )
            {
                ScChartHelper::FillProtectedChartRangesVector( rRangesVector, rDoc, pPage );
            }
        }
    }

    if ( pSysClipDoc )
    {
        ScDrawLayer::SetGlobalDrawPersist(nullptr);
        ScGlobal::SetClipDocName( rDoc.GetDocumentShell()->GetTitle( SFX_TITLE_FULLNAME ) );
    }
    pClipDoc->ExtendMerge( aRange, true );

    if ( pSysClipDoc )
    {
        ScDocShell* pDocSh = GetViewData().GetDocShell();
        TransferableObjectDescriptor aObjDesc;
        pDocSh->FillTransferableObjectDescriptor( aObjDesc );
        aObjDesc.maDisplayName = pDocSh->GetMedium()->GetURLObject().GetURLNoPass();
        // maSize is set in ScTransferObj ctor

        rtl::Reference<ScTransferObj> pTransferObj(new ScTransferObj( pSysClipDoc, std::move(aObjDesc) ));
        if ( ScGlobal::xDrawClipDocShellRef.is() )
        {
            SfxObjectShellRef aPersistRef(ScGlobal::xDrawClipDocShellRef);
            pTransferObj->SetDrawPersist( aPersistRef );// keep persist for ole objects alive
        }
        pTransferObj->CopyToClipboard( GetActiveWin() );
    }

    return true;
}

bool ScViewFunc::CopyToClipMultiRange( const ScDocument* pInputClipDoc, const ScRangeList& rRanges, bool bCut, bool bApi, bool bIncludeObjects )
{
    if (bCut)
    {
        // We don't support cutting of multi-selections.
        if (!bApi)
            ErrorMessage(STR_NOMULTISELECT);
        return false;
    }
    if (pInputClipDoc)
    {
        // TODO: What's this for?
        if (!bApi)
            ErrorMessage(STR_NOMULTISELECT);
        return false;
    }

    ScClipParam aClipParam( rRanges[0], bCut );
    aClipParam.maRanges = rRanges;
    ScDocument& rDoc = GetViewData().GetDocument();
    ScMarkData& rMark = GetViewData().GetMarkData();
    bool bDone = false;
    bool bSuccess = false;
    aClipParam.mbCutMode = false;

    do
    {
        ScDocumentUniquePtr pDocClip(new ScDocument(SCDOCMODE_CLIP));

        // Check for geometrical feasibility of the ranges.
        bool bValidRanges = true;
        ScRange const * p = &aClipParam.maRanges.front();
        SCCOL nPrevColDelta = 0;
        SCROW nPrevRowDelta = 0;
        SCCOL nPrevCol = p->aStart.Col();
        SCROW nPrevRow = p->aStart.Row();
        SCCOL nPrevColSize = p->aEnd.Col() - p->aStart.Col() + 1;
        SCROW nPrevRowSize = p->aEnd.Row() - p->aStart.Row() + 1;
        for ( size_t i = 1; i < aClipParam.maRanges.size(); ++i )
        {
            p = &aClipParam.maRanges[i];
            if ( rDoc.HasSelectedBlockMatrixFragment(
                p->aStart.Col(), p->aStart.Row(), p->aEnd.Col(), p->aEnd.Row(), rMark) )
            {
                if (!bApi)
                    ErrorMessage(STR_MATRIXFRAGMENTERR);
                return false;
            }

            SCCOL nColDelta = p->aStart.Col() - nPrevCol;
            SCROW nRowDelta = p->aStart.Row() - nPrevRow;

            if ((nColDelta && nRowDelta) || (nPrevColDelta && nRowDelta) || (nPrevRowDelta && nColDelta))
            {
                bValidRanges = false;
                break;
            }

            if (aClipParam.meDirection == ScClipParam::Unspecified)
            {
                if (nColDelta)
                    aClipParam.meDirection = ScClipParam::Column;
                if (nRowDelta)
                    aClipParam.meDirection = ScClipParam::Row;
            }

            SCCOL nColSize = p->aEnd.Col() - p->aStart.Col() + 1;
            SCROW nRowSize = p->aEnd.Row() - p->aStart.Row() + 1;

            if (aClipParam.meDirection == ScClipParam::Column && nRowSize != nPrevRowSize)
            {
                // column-oriented ranges must have identical row size.
                bValidRanges = false;
                break;
            }
            if (aClipParam.meDirection == ScClipParam::Row && nColSize != nPrevColSize)
            {
                // likewise, row-oriented ranges must have identical
                // column size.
                bValidRanges = false;
                break;
            }

            nPrevCol = p->aStart.Col();
            nPrevRow = p->aStart.Row();
            nPrevColDelta = nColDelta;
            nPrevRowDelta = nRowDelta;
            nPrevColSize  = nColSize;
            nPrevRowSize  = nRowSize;
        }
        if (!bValidRanges)
            break;
        rDoc.CopyToClip(aClipParam, pDocClip.get(), &rMark, false, bIncludeObjects );

        ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
        if ( pChangeTrack )
            pChangeTrack->ResetLastCut();   // no more cut-mode

        ScDocShell* pDocSh = GetViewData().GetDocShell();
        TransferableObjectDescriptor aObjDesc;
        pDocSh->FillTransferableObjectDescriptor( aObjDesc );
        aObjDesc.maDisplayName = pDocSh->GetMedium()->GetURLObject().GetURLNoPass();
        // maSize is set in ScTransferObj ctor

        rtl::Reference<ScTransferObj> pTransferObj(new ScTransferObj( std::move(pDocClip), std::move(aObjDesc) ));
        if ( ScGlobal::xDrawClipDocShellRef.is() )
        {
            SfxObjectShellRef aPersistRef(ScGlobal::xDrawClipDocShellRef);
            pTransferObj->SetDrawPersist( aPersistRef );    // keep persist for ole objects alive
        }
        pTransferObj->CopyToClipboard( GetActiveWin() );    // system clipboard

        bSuccess = true;
    }
    while (false);

    if (!bSuccess && !bApi)
        ErrorMessage(STR_NOMULTISELECT);

    bDone = bSuccess;

    return bDone;
}

rtl::Reference<ScTransferObj> ScViewFunc::CopyToTransferable()
{
    ScRange aRange;
    auto eMarkType = GetViewData().GetSimpleArea( aRange );
    if ( eMarkType == SC_MARK_SIMPLE || eMarkType == SC_MARK_SIMPLE_FILTERED )
    {
        ScDocument& rDoc = GetViewData().GetDocument();
        ScMarkData& rMark = GetViewData().GetMarkData();
        if ( !rDoc.HasSelectedBlockMatrixFragment(
                        aRange.aStart.Col(), aRange.aStart.Row(),
                        aRange.aEnd.Col(),   aRange.aEnd.Row(),
                        rMark ) )
        {
            ScDocumentUniquePtr pClipDoc(new ScDocument( SCDOCMODE_CLIP ));    // create one (deleted by ScTransferObj)

            bool bAnyOle = rDoc.HasOLEObjectsInArea( aRange, &rMark );
            ScDrawLayer::SetGlobalDrawPersist( ScTransferObj::SetDrawClipDoc( bAnyOle ) );

            ScClipParam aClipParam(aRange, false);
            rDoc.CopyToClip(aClipParam, pClipDoc.get(), &rMark, false, true);

            ScDrawLayer::SetGlobalDrawPersist(nullptr);
            pClipDoc->ExtendMerge( aRange, true );

            ScDocShell* pDocSh = GetViewData().GetDocShell();
            TransferableObjectDescriptor aObjDesc;
            pDocSh->FillTransferableObjectDescriptor( aObjDesc );
            aObjDesc.maDisplayName = pDocSh->GetMedium()->GetURLObject().GetURLNoPass();
            return new ScTransferObj( std::move(pClipDoc), std::move(aObjDesc) );
        }
    }
    else if (eMarkType == SC_MARK_MULTI)
    {
        ScDocumentUniquePtr pClipDoc(new ScDocument(SCDOCMODE_CLIP));
        // This takes care of the input line and calls CopyToClipMultiRange() for us.
        CopyToClip(pClipDoc.get(), aRange, /*bCut=*/false, /*bApi=*/true);
        TransferableObjectDescriptor aObjDesc;
        return new ScTransferObj(std::move(pClipDoc), std::move(aObjDesc));
    }

    return nullptr;
}

//      P A S T E

void ScViewFunc::PasteDraw()
{
    ScViewData& rViewData = GetViewData();
    SCCOL nPosX = rViewData.GetCurX();
    SCROW nPosY = rViewData.GetCurY();
    vcl::Window* pWin = GetActiveWin();
    Point aPos = pWin->PixelToLogic( rViewData.GetScrPos( nPosX, nPosY,
                                     rViewData.GetActivePart() ) );
    const ScDrawTransferObj* pDrawClip = ScDrawTransferObj::GetOwnClipboard(ScTabViewShell::GetClipData(rViewData.GetActiveWin()));
    if (pDrawClip)
    {
        const OUString& aSrcShellID = pDrawClip->GetShellID();
        OUString aDestShellID = SfxObjectShell::CreateShellID(rViewData.GetDocShell());
        PasteDraw(aPos, pDrawClip->GetModel(), false, aSrcShellID, aDestShellID);
    }
}

void ScViewFunc::PasteFromSystem(bool useSavedPrefs)
{
    UpdateInputLine();

    vcl::Window* pWin = GetActiveWin();
    css::uno::Reference<css::datatransfer::XTransferable2> xTransferable2(ScTabViewShell::GetClipData(pWin));
    const ScTransferObj* pOwnClip = ScTransferObj::GetOwnClipboard(xTransferable2);
    // keep a reference in case the clipboard is changed during PasteFromClip
    const ScDrawTransferObj* pDrawClip = ScDrawTransferObj::GetOwnClipboard(xTransferable2);
    if (pOwnClip)
    {
        PasteFromClip( InsertDeleteFlags::ALL, pOwnClip->GetDocument(),
                        ScPasteFunc::NONE, false, false, false, INS_NONE, InsertDeleteFlags::NONE,
                        true );     // allow warning dialog
    }
    else if (pDrawClip)
        PasteDraw();
    else
    {
        TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromSystemClipboard( pWin ) );

        {
            SotClipboardFormatId nBiff12= SotExchange::RegisterFormatName(u"Biff12"_ustr);
            SotClipboardFormatId nBiff8 = SotExchange::RegisterFormatName(u"Biff8"_ustr);
            SotClipboardFormatId nBiff5 = SotExchange::RegisterFormatName(u"Biff5"_ustr);

            SotClipboardFormatId nFormat; // output param for GetExchangeAction
            sal_uInt8 nEventAction;      // output param for GetExchangeAction

            uno::Reference<css::datatransfer::XTransferable> xTransferable( aDataHelper.GetXTransferable() );
            sal_uInt8 nAction = SotExchange::GetExchangeAction(
                                    aDataHelper.GetDataFlavorExVector(),
                                    SotExchangeDest::SCDOC_FREE_AREA,
                                    EXCHG_IN_ACTION_COPY,
                                    EXCHG_IN_ACTION_DEFAULT,
                                    nFormat, nEventAction, SotClipboardFormatId::NONE,
                                    &xTransferable );

            if ( nAction != EXCHG_INOUT_ACTION_NONE )
            {
                switch( nAction )
                {
                case EXCHG_OUT_ACTION_INSERT_SVXB:
                case EXCHG_OUT_ACTION_INSERT_GDIMETAFILE:
                case EXCHG_OUT_ACTION_INSERT_BITMAP:
                case EXCHG_OUT_ACTION_INSERT_GRAPH:
                    // SotClipboardFormatId::BITMAP
                    // SotClipboardFormatId::PNG
                    // SotClipboardFormatId::GDIMETAFILE
                    // SotClipboardFormatId::SVXB
                    PasteFromSystem(nFormat);
                    break;
                default:
                    nAction = EXCHG_INOUT_ACTION_NONE;
                }
            }

            if ( nAction == EXCHG_INOUT_ACTION_NONE )
            {
                //  first SvDraw-model, then drawing
                //  (only one drawing is allowed)

                if (aDataHelper.HasFormat( SotClipboardFormatId::DRAWING ))
                {
                    // special case for tables from drawing
                    if( aDataHelper.HasFormat( SotClipboardFormatId::RTF ) )
                    {
                        PasteFromSystem( SotClipboardFormatId::RTF );
                    }
                    else if( aDataHelper.HasFormat( SotClipboardFormatId::RICHTEXT ) )
                    {
                        PasteFromSystem( SotClipboardFormatId::RICHTEXT );
                    }
                    else
                    {
                        PasteFromSystem( SotClipboardFormatId::DRAWING );
                    }
                }
                else if (aDataHelper.HasFormat( SotClipboardFormatId::EMBED_SOURCE ))
                {
                    //  If it's a Writer object, insert RTF instead of OLE

                    //  Else, if the class id is all-zero, and SYLK is available,
                    //  it probably is spreadsheet cells that have been put
                    //  on the clipboard by OOo, so use the SYLK. (fdo#31077)

                    bool bDoRtf = false;
                    TransferableObjectDescriptor aObjDesc;
                    if( aDataHelper.GetTransferableObjectDescriptor( SotClipboardFormatId::OBJECTDESCRIPTOR, aObjDesc ) )
                    {
                        bDoRtf = ( ( aObjDesc.maClassName == SvGlobalName( SO3_SW_CLASSID ) ||
                                     aObjDesc.maClassName == SvGlobalName( SO3_SWWEB_CLASSID ) )
                                   && ( aDataHelper.HasFormat( SotClipboardFormatId::RTF ) || aDataHelper.HasFormat( SotClipboardFormatId::RICHTEXT ) ) );
                    }
                    if ( bDoRtf )
                        PasteFromSystem( aDataHelper.HasFormat( SotClipboardFormatId::RTF ) ? SotClipboardFormatId::RTF : SotClipboardFormatId::RICHTEXT );
                    else if ( aObjDesc.maClassName == SvGlobalName( 0,0,0,0,0,0,0,0,0,0,0 )
                              && aDataHelper.HasFormat( SotClipboardFormatId::SYLK ))
                        PasteFromSystem( SotClipboardFormatId::SYLK );
                    else
                        PasteFromSystem( SotClipboardFormatId::EMBED_SOURCE );
                }
                else if (aDataHelper.HasFormat( SotClipboardFormatId::LINK_SOURCE ))
                    PasteFromSystem( SotClipboardFormatId::LINK_SOURCE );
                    // the following format can not affect scenario from #89579#
                else if (aDataHelper.HasFormat( SotClipboardFormatId::EMBEDDED_OBJ_OLE ))
                    PasteFromSystem( SotClipboardFormatId::EMBEDDED_OBJ_OLE );
                    // SotClipboardFormatId::PRIVATE no longer here (can't work if pOwnClip is NULL)
                else if (aDataHelper.HasFormat(nBiff12))      // before xxx_OLE formats
                    PasteFromSystem(nBiff12);
                else if (aDataHelper.HasFormat(nBiff8))
                    PasteFromSystem(nBiff8);
                else if (aDataHelper.HasFormat(nBiff5))
                    PasteFromSystem(nBiff5);
                else if (aDataHelper.HasFormat(SotClipboardFormatId::RTF))
                    PasteFromSystem(SotClipboardFormatId::RTF);
                else if (aDataHelper.HasFormat(SotClipboardFormatId::RICHTEXT))
                    PasteFromSystem(SotClipboardFormatId::RICHTEXT);
                else if (aDataHelper.HasFormat(SotClipboardFormatId::HTML))
                    PasteFromSystem(SotClipboardFormatId::HTML, false, useSavedPrefs);
                else if (aDataHelper.HasFormat(SotClipboardFormatId::BITMAP))
                    PasteFromSystem(SotClipboardFormatId::BITMAP);
                else if (aDataHelper.HasFormat(SotClipboardFormatId::HTML_SIMPLE))
                    PasteFromSystem(SotClipboardFormatId::HTML_SIMPLE);
                else if (aDataHelper.HasFormat(SotClipboardFormatId::SYLK))
                    PasteFromSystem(SotClipboardFormatId::SYLK);
                else if (aDataHelper.HasFormat(SotClipboardFormatId::STRING_TSVC))
                    PasteFromSystem(SotClipboardFormatId::STRING_TSVC);
                else if (aDataHelper.HasFormat(SotClipboardFormatId::STRING))
                    PasteFromSystem(SotClipboardFormatId::STRING);
                // xxx_OLE formats come last, like in SotExchange tables
                else if (aDataHelper.HasFormat( SotClipboardFormatId::EMBED_SOURCE_OLE ))
                    PasteFromSystem( SotClipboardFormatId::EMBED_SOURCE_OLE );
                else if (aDataHelper.HasFormat( SotClipboardFormatId::LINK_SOURCE_OLE ))
                    PasteFromSystem( SotClipboardFormatId::LINK_SOURCE_OLE );
            }
        }
    }
    //  no exception-> SID_PASTE has FastCall-flag from idl
    //  will be called in case of empty clipboard (#42531#)
}

void ScViewFunc::PasteFromTransferable( const uno::Reference<datatransfer::XTransferable>& rxTransferable )
{
    if (auto pOwnClip = dynamic_cast<ScTransferObj*>(rxTransferable.get()))
    {
        PasteFromClip( InsertDeleteFlags::ALL, pOwnClip->GetDocument(),
                        ScPasteFunc::NONE, false, false, false, INS_NONE, InsertDeleteFlags::NONE,
                        true );     // allow warning dialog
    }
    else if (auto pDrawClip = dynamic_cast<ScDrawTransferObj*>(rxTransferable.get()))
    {
        ScViewData& rViewData = GetViewData();
        SCCOL nPosX = rViewData.GetCurX();
        SCROW nPosY = rViewData.GetCurY();
        vcl::Window* pWin = GetActiveWin();
        Point aPos = pWin->PixelToLogic( rViewData.GetScrPos( nPosX, nPosY, rViewData.GetActivePart() ) );
        PasteDraw(
            aPos, pDrawClip->GetModel(), false,
            pDrawClip->GetShellID(), SfxObjectShell::CreateShellID(rViewData.GetDocShell()));
    }
    else
    {
            TransferableDataHelper aDataHelper( rxTransferable );
            SotClipboardFormatId nBiff12 = SotExchange::RegisterFormatName(u"Biff12"_ustr);
            SotClipboardFormatId nBiff8 = SotExchange::RegisterFormatName(u"Biff8"_ustr);
            SotClipboardFormatId nBiff5 = SotExchange::RegisterFormatName(u"Biff5"_ustr);
            SotClipboardFormatId nFormatId = SotClipboardFormatId::NONE;
                //  first SvDraw-model, then drawing
                //  (only one drawing is allowed)

            if (aDataHelper.HasFormat( SotClipboardFormatId::DRAWING ))
                nFormatId = SotClipboardFormatId::DRAWING;
            else if (aDataHelper.HasFormat( SotClipboardFormatId::SVXB ))
                nFormatId = SotClipboardFormatId::SVXB;
            else if (aDataHelper.HasFormat( SotClipboardFormatId::EMBED_SOURCE ))
            {
                //  If it's a Writer object, insert RTF instead of OLE
                bool bDoRtf = false;
                TransferableObjectDescriptor aObjDesc;
                if( aDataHelper.GetTransferableObjectDescriptor( SotClipboardFormatId::OBJECTDESCRIPTOR, aObjDesc ) )
                {
                    bDoRtf = ( ( aObjDesc.maClassName == SvGlobalName( SO3_SW_CLASSID ) ||
                                 aObjDesc.maClassName == SvGlobalName( SO3_SWWEB_CLASSID ) )
                               && ( aDataHelper.HasFormat( SotClipboardFormatId::RTF ) || aDataHelper.HasFormat( SotClipboardFormatId::RICHTEXT ) ));
                }
                if ( bDoRtf )
                    nFormatId = aDataHelper.HasFormat( SotClipboardFormatId::RTF ) ? SotClipboardFormatId::RTF : SotClipboardFormatId::RICHTEXT;
                else
                    nFormatId = SotClipboardFormatId::EMBED_SOURCE;
            }
            else if (aDataHelper.HasFormat( SotClipboardFormatId::LINK_SOURCE ))
                nFormatId = SotClipboardFormatId::LINK_SOURCE;
            // the following format can not affect scenario from #89579#
            else if (aDataHelper.HasFormat( SotClipboardFormatId::EMBEDDED_OBJ_OLE ))
                nFormatId = SotClipboardFormatId::EMBEDDED_OBJ_OLE;
            // SotClipboardFormatId::PRIVATE no longer here (can't work if pOwnClip is NULL)
            else if (aDataHelper.HasFormat(nBiff12))      // before xxx_OLE formats
                nFormatId = nBiff12;
            else if (aDataHelper.HasFormat(nBiff8))
                nFormatId = nBiff8;
            else if (aDataHelper.HasFormat(nBiff5))
                nFormatId = nBiff5;
            else if (aDataHelper.HasFormat(SotClipboardFormatId::RTF))
                nFormatId = SotClipboardFormatId::RTF;
            else if (aDataHelper.HasFormat(SotClipboardFormatId::RICHTEXT))
                nFormatId = SotClipboardFormatId::RICHTEXT;
            else if (aDataHelper.HasFormat(SotClipboardFormatId::HTML))
                nFormatId = SotClipboardFormatId::HTML;
            else if (aDataHelper.HasFormat(SotClipboardFormatId::HTML_SIMPLE))
                nFormatId = SotClipboardFormatId::HTML_SIMPLE;
            else if (aDataHelper.HasFormat(SotClipboardFormatId::SYLK))
                nFormatId = SotClipboardFormatId::SYLK;
            else if (aDataHelper.HasFormat(SotClipboardFormatId::STRING_TSVC))
                nFormatId = SotClipboardFormatId::STRING_TSVC;
            else if (aDataHelper.HasFormat(SotClipboardFormatId::STRING))
                nFormatId = SotClipboardFormatId::STRING;
            else if (aDataHelper.HasFormat(SotClipboardFormatId::GDIMETAFILE))
                nFormatId = SotClipboardFormatId::GDIMETAFILE;
            else if (aDataHelper.HasFormat(SotClipboardFormatId::BITMAP))
                nFormatId = SotClipboardFormatId::BITMAP;
            // xxx_OLE formats come last, like in SotExchange tables
            else if (aDataHelper.HasFormat( SotClipboardFormatId::EMBED_SOURCE_OLE ))
                nFormatId = SotClipboardFormatId::EMBED_SOURCE_OLE;
            else if (aDataHelper.HasFormat( SotClipboardFormatId::LINK_SOURCE_OLE ))
                nFormatId = SotClipboardFormatId::LINK_SOURCE_OLE;
            else
                return;

            PasteDataFormat( nFormatId, aDataHelper.GetTransferable(),
                GetViewData().GetCurX(), GetViewData().GetCurY(), nullptr );
    }
}

bool ScViewFunc::PasteFromSystem( SotClipboardFormatId nFormatId, bool bApi, bool useSavedPrefs )
{
    UpdateInputLine();

    bool bRet = true;
    vcl::Window* pWin = GetActiveWin();
    // keep a reference in case the clipboard is changed during PasteFromClip
    const ScTransferObj* pOwnClip = ScTransferObj::GetOwnClipboard(ScTabViewShell::GetClipData(pWin));
    if ( nFormatId == SotClipboardFormatId::NONE && pOwnClip )
    {
        PasteFromClip( InsertDeleteFlags::ALL, pOwnClip->GetDocument(),
                        ScPasteFunc::NONE, false, false, false, INS_NONE, InsertDeleteFlags::NONE,
                        !bApi );        // allow warning dialog
    }
    else
    {
        TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromSystemClipboard( pWin ) );
        if ( !aDataHelper.GetTransferable().is() )
            return false;

        SCCOL nPosX = 0;
        SCROW nPosY = 0;

        ScViewData& rViewData = GetViewData();
        ScRange aRange;
        if ( rViewData.GetSimpleArea( aRange ) == SC_MARK_SIMPLE )
        {
            nPosX = aRange.aStart.Col();
            nPosY = aRange.aStart.Row();
        }
        else
        {
            nPosX = rViewData.GetCurX();
            nPosY = rViewData.GetCurY();
        }

        bRet = PasteDataFormat( nFormatId, aDataHelper.GetTransferable(),
                                nPosX, nPosY,
                                nullptr, false, !bApi, useSavedPrefs );       // allow warning dialog

        if ( !bRet && !bApi )
        {
            ErrorMessage(STR_PASTE_ERROR);
        }
        else if (comphelper::LibreOfficeKit::isActive())
        {
            ScTabViewShell* pTabViewShell = rViewData.GetViewShell();
            pTabViewShell->OnLOKSetWidthOrHeight(rViewData.GetCurX(), true);
            pTabViewShell->OnLOKSetWidthOrHeight(rViewData.GetCurY(), false);

            ScTabViewShell::notifyAllViewsSheetGeomInvalidation(pTabViewShell, true /* bColumns */, true /* bRows */,
                true /* bSizes */, false /* bHidden */, false /* bFiltered */, false /* bGroups */, rViewData.CurrentTabForData());
        }
    }
    return bRet;
}

//      P A S T E

bool ScViewFunc::PasteOnDrawObjectLinked(
    const uno::Reference<datatransfer::XTransferable>& rxTransferable,
    SdrObject& rHitObj)
{
    TransferableDataHelper aDataHelper( rxTransferable );

    if ( aDataHelper.HasFormat( SotClipboardFormatId::SVXB ) )
    {
        if (ScDrawView* pScDrawView = GetScDrawView())
            if (std::unique_ptr<SvStream> xStm = aDataHelper.GetSotStorageStream( SotClipboardFormatId::SVXB ) )
            {
                Graphic aGraphic;
                TypeSerializer aSerializer(*xStm);
                aSerializer.readGraphic(aGraphic);

                const OUString aBeginUndo(ScResId(STR_UNDO_DRAGDROP));

                if(pScDrawView->ApplyGraphicToObject( rHitObj, aGraphic, aBeginUndo, u""_ustr ))
                {
                    return true;
                }
            }
    }
    else if ( aDataHelper.HasFormat( SotClipboardFormatId::GDIMETAFILE ) )
    {
        GDIMetaFile aMtf;
        ScDrawView* pScDrawView = GetScDrawView();

        if( pScDrawView && aDataHelper.GetGDIMetaFile( SotClipboardFormatId::GDIMETAFILE, aMtf ) )
        {
            const OUString aBeginUndo(ScResId(STR_UNDO_DRAGDROP));

            if(pScDrawView->ApplyGraphicToObject( rHitObj, Graphic(aMtf), aBeginUndo, u""_ustr ))
            {
                return true;
            }
        }
    }
    else if ( aDataHelper.HasFormat( SotClipboardFormatId::BITMAP ) || aDataHelper.HasFormat( SotClipboardFormatId::PNG ) )
    {
        Bitmap aBmp;
        ScDrawView* pScDrawView = GetScDrawView();

        if (pScDrawView && aDataHelper.GetBitmap(SotClipboardFormatId::BITMAP, aBmp))
        {
            const OUString aBeginUndo(ScResId(STR_UNDO_DRAGDROP));

            if(pScDrawView->ApplyGraphicToObject( rHitObj, Graphic(aBmp), aBeginUndo, u""_ustr ))
            {
                return true;
            }
        }
    }

    return false;
}

static bool lcl_SelHasAttrib( const ScDocument& rDoc, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                        const ScMarkData& rTabSelection, HasAttrFlags nMask )
{
    return std::any_of(rTabSelection.begin(), rTabSelection.end(),
        [&](const SCTAB& rTab) { return rDoc.HasAttrib( nCol1, nRow1, rTab, nCol2, nRow2, rTab, nMask ); });
}

//  paste into sheet:

//  internal paste

namespace {

bool checkDestRangeForOverwrite(InsertDeleteFlags nFlags, const ScRangeList& rDestRanges,
                                const ScDocument& rDoc, const ScMarkData& rMark,
                                weld::Window* pParentWnd)
{
    bool bIsEmpty = true;
    size_t nRangeSize = rDestRanges.size();

    for (const auto& rTab : rMark)
    {
        for (size_t i = 0; i < nRangeSize && bIsEmpty; ++i)
        {
            const ScRange& rRange = rDestRanges[i];
            // tdf#158110 - check if just the ADDNOTES flag is present without any other content
            if ((nFlags & InsertDeleteFlags::ADDNOTES) == InsertDeleteFlags::ADDNOTES
                && (nFlags & (InsertDeleteFlags::CONTENTS & ~InsertDeleteFlags::NOTE))
                       == InsertDeleteFlags::NONE)
                bIsEmpty = rDoc.IsNotesBlockEmpty(rRange.aStart.Col(), rRange.aStart.Row(),
                                                  rRange.aEnd.Col(), rRange.aEnd.Row(), rTab);
            else
                bIsEmpty = rDoc.IsBlockEmpty(rRange.aStart.Col(), rRange.aStart.Row(),
                                             rRange.aEnd.Col(), rRange.aEnd.Row(), rTab);
        }
        if (!bIsEmpty)
            break;
    }

    if (!bIsEmpty)
    {
        ScReplaceWarnBox aBox(pParentWnd);
        if (aBox.run() != RET_YES)
        {
            //  changing the configuration is within the ScReplaceWarnBox
            return false;
        }
    }
    return true;
}

}

bool ScViewFunc::PasteFromClip( InsertDeleteFlags nFlags, ScDocument* pClipDoc,
                                ScPasteFunc nFunction, bool bSkipEmptyCells,
                                bool bTranspose, bool bAsLink,
                                InsCellCmd eMoveMode, InsertDeleteFlags nUndoExtraFlags,
                                bool bAllowDialogs )
{
    if (!pClipDoc)
    {
        OSL_FAIL("PasteFromClip: pClipDoc=0 not allowed");
        return false;
    }

    if (GetViewData().SelectionForbidsPaste(pClipDoc))
        return false;

    //  undo: save all or no content
    InsertDeleteFlags nContFlags = InsertDeleteFlags::NONE;
    if (nFlags & InsertDeleteFlags::CONTENTS)
        nContFlags |= InsertDeleteFlags::CONTENTS;
    if (nFlags & InsertDeleteFlags::ATTRIB)
        nContFlags |= InsertDeleteFlags::ATTRIB;
    // move attributes to undo without copying them from clip to doc
    InsertDeleteFlags nUndoFlags = nContFlags;
    if (nUndoExtraFlags & InsertDeleteFlags::ATTRIB)
        nUndoFlags |= InsertDeleteFlags::ATTRIB;
    // do not copy note captions into undo document
    nUndoFlags |= InsertDeleteFlags::NOCAPTIONS;

    ScClipParam& rClipParam = pClipDoc->GetClipParam();
    if (rClipParam.isMultiRange())
    {
        // Source data is multi-range.
        return PasteMultiRangesFromClip(nFlags, pClipDoc, nFunction, bSkipEmptyCells, bTranspose,
                                        bAsLink, bAllowDialogs, eMoveMode, nUndoFlags);
    }

    ScMarkData& rMark = GetViewData().GetMarkData();
    if (rMark.IsMultiMarked())
    {
        // Source data is single-range but destination is multi-range.
        return PasteFromClipToMultiRanges(
            nFlags, pClipDoc, nFunction, bSkipEmptyCells, bTranspose, bAsLink, bAllowDialogs,
            eMoveMode, nUndoFlags);
    }

    bool bCutMode = pClipDoc->IsCutMode();      // if transposing, take from original clipdoc
    bool bIncludeFiltered = bCutMode;

    // paste drawing: also if InsertDeleteFlags::NOTE is set (to create drawing layer for note captions)
    bool bPasteDraw = ( pClipDoc->GetDrawLayer() && ( nFlags & (InsertDeleteFlags::OBJECTS|InsertDeleteFlags::NOTE) ) );

    ScDocShellRef aTransShellRef;   // for objects in xTransClip - must remain valid as long as xTransClip
    ScDocument* pOrigClipDoc = nullptr;
    ScDocumentUniquePtr xTransClip;
    if ( bTranspose )
    {
        SCCOL nX;
        SCROW nY;
        // include filtered rows until TransposeClip can skip them
        pClipDoc->GetClipArea( nX, nY, true );
        if ( nY > static_cast<sal_Int32>(pClipDoc->MaxCol()) )                      // too many lines for transpose
        {
            ErrorMessage(STR_PASTE_FULL);
            return false;
        }
        pOrigClipDoc = pClipDoc;        // refs

        if ( bPasteDraw )
        {
            aTransShellRef = new ScDocShell;        // DocShell needs a Ref immediately
            aTransShellRef->DoInitNew();
        }
        ScDrawLayer::SetGlobalDrawPersist( aTransShellRef.get() );

        xTransClip.reset( new ScDocument( SCDOCMODE_CLIP ));
        pClipDoc->TransposeClip(xTransClip.get(), nFlags, bAsLink, bIncludeFiltered);
        pClipDoc = xTransClip.get();

        ScDrawLayer::SetGlobalDrawPersist(nullptr);
    }

    // TODO: position this call better for performance.
    ResetAutoSpellForContentChange();

    SCCOL nStartCol;
    SCROW nStartRow;
    SCTAB nStartTab;
    SCCOL nEndCol;
    SCROW nEndRow;
    SCTAB nEndTab;
    SCCOL nClipSizeX;
    SCROW nClipSizeY;
    pClipDoc->GetClipArea( nClipSizeX, nClipSizeY, true );      // size in clipboard doc

    //  size in target doc: include filtered rows only if CutMode is set
    SCCOL nDestSizeX;
    SCROW nDestSizeY;
    pClipDoc->GetClipArea( nDestSizeX, nDestSizeY, bIncludeFiltered );

    ScDocument& rDoc = GetViewData().GetDocument();
    ScDocShell* pDocSh = GetViewData().GetDocShell();
    SfxUndoManager* pUndoMgr = pDocSh->GetUndoManager();
    const bool bRecord(rDoc.IsUndoEnabled());

    ScDocShellModificator aModificator( *pDocSh );

    ScRange aMarkRange;
    ScMarkData aFilteredMark( rMark);   // local copy for all modifications
    ScMarkType eMarkType = GetViewData().GetSimpleArea( aMarkRange, aFilteredMark);
    bool bMarkIsFiltered = (eMarkType == SC_MARK_SIMPLE_FILTERED);
    bool bNoPaste = ((eMarkType != SC_MARK_SIMPLE && !bMarkIsFiltered) ||
            (bMarkIsFiltered && (eMoveMode != INS_NONE || bAsLink)));

    if (!bNoPaste)
    {
        if (!rMark.IsMarked())
        {
            // Create a selection with clipboard row count and check that for
            // filtered.
            nStartCol = GetViewData().GetCurX();
            nStartRow = GetViewData().GetCurY();
            nStartTab = GetViewData().CurrentTabForData();
            nEndCol = nStartCol + nDestSizeX;
            nEndRow = nStartRow + nDestSizeY;
            nEndTab = nStartTab;
            aMarkRange = ScRange( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab);
            if (ScViewUtil::HasFiltered(aMarkRange, rDoc))
            {
                bMarkIsFiltered = true;
                // Fit to clipboard's row count unfiltered rows. If there is no
                // fit assume that pasting is not possible. Note that nDestSizeY is
                // size-1 (difference).
                if (!ScViewUtil::FitToUnfilteredRows(aMarkRange, rDoc, nDestSizeY+1))
                    bNoPaste = true;
            }
            aFilteredMark.SetMarkArea( aMarkRange);
        }
        else
        {
            // Expand the marked area when the destination area is larger than the
            // current selection, to get the undo do the right thing. (i#106711)
            ScRange aRange = aFilteredMark.GetMarkArea();
            if( (aRange.aEnd.Col() - aRange.aStart.Col()) < nDestSizeX )
            {
                aRange.aEnd.SetCol(aRange.aStart.Col() + nDestSizeX);
                aFilteredMark.SetMarkArea(aRange);
            }
        }
    }

    if (bNoPaste)
    {
        ErrorMessage(STR_MSSG_PASTEFROMCLIP_0);
        return false;
    }

    SCROW nUnfilteredRows = aMarkRange.aEnd.Row() - aMarkRange.aStart.Row() + 1;
    ScRangeList aRangeList;
    if (bMarkIsFiltered)
    {
        ScViewUtil::UnmarkFiltered(aFilteredMark, rDoc);
        aFilteredMark.FillRangeListWithMarks( &aRangeList, false);
        nUnfilteredRows = 0;
        size_t ListSize = aRangeList.size();
        for ( size_t i = 0; i < ListSize; ++i )
        {
            ScRange & r = aRangeList[i];
            nUnfilteredRows += r.aEnd.Row() - r.aStart.Row() + 1;
        }
#if 0
        /* This isn't needed but could be a desired restriction. */
        // For filtered, destination rows have to be an exact multiple of
        // source rows. Note that nDestSizeY is size-1 (difference), so
        // nDestSizeY==0 fits always.
        if ((nUnfilteredRows % (nDestSizeY+1)) != 0)
        {
            /* FIXME: this should be a more descriptive error message then. */
            ErrorMessage(STR_MSSG_PASTEFROMCLIP_0);
            return false;
        }
#endif
    }

    // Also for a filtered selection the area is used, for undo et al.
    if ( aFilteredMark.IsMarked() || bMarkIsFiltered )
    {
        aMarkRange.GetVars( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab);
        SCCOL nBlockAddX = nEndCol-nStartCol;
        SCROW nBlockAddY = nEndRow-nStartRow;

        // request, if the selection is greater than one row/column, but smaller
        // as the Clipboard (then inserting is done beyond the selection)

        //  ClipSize is not size, but difference
        if ( ( nBlockAddX != 0 && nBlockAddX < nDestSizeX ) ||
             ( nBlockAddY != 0 && nBlockAddY < nDestSizeY ) ||
             ( bMarkIsFiltered && nUnfilteredRows < nDestSizeY+1 ) )
        {
            ScWaitCursorOff aWaitOff( GetFrameWin() );
            OUString aMessage = ScResId( STR_PASTE_BIGGER );

            std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(GetViewData().GetDialogParent(),
                                                           VclMessageType::Question, VclButtonsType::YesNo,
                                                           aMessage));
            xQueryBox->set_default_response(RET_NO);
            if (xQueryBox->run() != RET_YES)
            {
                return false;
            }
        }

        if (nBlockAddX <= nDestSizeX)
            nEndCol = nStartCol + nDestSizeX;

        if (nBlockAddY <= nDestSizeY)
        {
            nEndRow = nStartRow + nDestSizeY;
            if (bMarkIsFiltered || nEndRow > aMarkRange.aEnd.Row())
            {
                // Same as above if nothing was marked: re-fit selection to
                // unfiltered rows. Extending the selection actually may
                // introduce filtered rows where there weren't any before, so
                // we also need to test for that.
                aMarkRange = ScRange( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab);
                if (bMarkIsFiltered || ScViewUtil::HasFiltered(aMarkRange, rDoc))
                {
                    bMarkIsFiltered = true;
                    // Worst case: all rows up to the end of the sheet are filtered.
                    if (!ScViewUtil::FitToUnfilteredRows(aMarkRange, rDoc, nDestSizeY+1))
                    {
                        ErrorMessage(STR_PASTE_FULL);
                        return false;
                    }
                }
                aMarkRange.GetVars( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab);
                aFilteredMark.SetMarkArea( aMarkRange);
                if (bMarkIsFiltered)
                {
                    ScViewUtil::UnmarkFiltered(aFilteredMark, rDoc);
                    aFilteredMark.FillRangeListWithMarks( &aRangeList, true);
                }
            }
        }
    }
    else
    {
        nStartCol = GetViewData().GetCurX();
        nStartRow = GetViewData().GetCurY();
        nStartTab = GetViewData().CurrentTabForData();
        nEndCol = nStartCol + nDestSizeX;
        nEndRow = nStartRow + nDestSizeY;
        nEndTab = nStartTab;
    }

    bool bOffLimits = !rDoc.ValidCol(nEndCol) || !rDoc.ValidRow(nEndRow);

    //  target-range, as displayed:
    ScRange aUserRange( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab );
    tools::Long nRangeWidth = GetViewData().GetDocShell()->GetDocument().GetColWidth(nStartCol,nEndCol,nStartTab);

    //  should lines be inserted?
    //  ( too large nEndCol/nEndRow are detected below)
    bool bInsertCells = ( eMoveMode != INS_NONE && !bOffLimits );
    if ( bInsertCells )
    {
        //  Instead of EnterListAction, the paste undo action is merged into the
        //  insert action, so Repeat can insert the right cells

        MarkRange( aUserRange );            // set through CopyFromClip

        // CutMode is reset on insertion of cols/rows but needed again on cell move
        bool bCut = pClipDoc->IsCutMode();
        if (!InsertCells( eMoveMode, bRecord, true ))   // is inserting possible?
        {
            return false;
            //  #i21036# EnterListAction isn't used, and InsertCells doesn't insert
            //  its undo action on failure, so no undo handling is needed here
        }
        if ( bCut )
            pClipDoc->SetCutMode( bCut );
    }
    else if (!bOffLimits)
    {
        bool bAskIfNotEmpty = bAllowDialogs &&
                                ( nFlags & InsertDeleteFlags::CONTENTS ) &&
                                nFunction == ScPasteFunc::NONE &&
                                ScModule::get()->GetInputOptions().GetReplaceCellsWarn();
        if ( bAskIfNotEmpty )
        {
            ScRangeList aTestRanges(aUserRange);
            if (!checkDestRangeForOverwrite(nFlags, aTestRanges, rDoc, aFilteredMark, GetViewData().GetDialogParent()))
                return false;
        }
    }

    SCCOL nClipStartX;                      // enlarge clipboard-range
    SCROW nClipStartY;
    pClipDoc->GetClipStart( nClipStartX, nClipStartY );
    SCCOL nUndoEndCol = nClipStartX + nClipSizeX;
    SCROW nUndoEndRow = nClipStartY + nClipSizeY;   // end of source area in clipboard document
    bool bClipOver = false;
    // #i68690# ExtendMerge for the clip doc must be called with the clipboard's sheet numbers.
    // The same end column/row can be used for all calls because the clip doc doesn't contain
    // content outside the clip area.
    for (SCTAB nClipTab=0; nClipTab < pClipDoc->GetTableCount(); nClipTab++)
        if ( pClipDoc->HasTable(nClipTab) )
            if ( pClipDoc->ExtendMerge( nClipStartX,nClipStartY, nUndoEndCol,nUndoEndRow, nClipTab ) )
                bClipOver = true;
    nUndoEndCol -= nClipStartX + nClipSizeX;
    nUndoEndRow -= nClipStartY + nClipSizeY;        // now contains only the difference added by ExtendMerge
    nUndoEndCol = sal::static_int_cast<SCCOL>( nUndoEndCol + nEndCol );
    nUndoEndRow = sal::static_int_cast<SCROW>( nUndoEndRow + nEndRow ); // destination area, expanded for merged cells

    if (nUndoEndCol>pClipDoc->MaxCol() || nUndoEndRow>pClipDoc->MaxRow())
    {
        ErrorMessage(STR_PASTE_FULL);
        return false;
    }

    rDoc.ExtendMergeSel( nStartCol,nStartRow, nUndoEndCol,nUndoEndRow, aFilteredMark );

        //  check cell-protection

    ScEditableTester aTester = ScEditableTester::CreateAndTestBlock(rDoc, nStartTab, nStartCol, nStartRow, nUndoEndCol, nUndoEndRow);
    if (!aTester.IsEditable())
    {
        ErrorMessage(aTester.GetMessageId());
        return false;
    }

        //! check overlapping
        //! just check truly intersection !!!!!!!

    ScDocFunc& rDocFunc = pDocSh->GetDocFunc();
    if ( bRecord )
    {
        OUString aUndo = ScResId( pClipDoc->IsCutMode() ? STR_UNDO_MOVE : STR_UNDO_COPY );
        pUndoMgr->EnterListAction( aUndo, aUndo, 0, GetViewData().GetViewShell()->GetViewShellId() );
    }

    if (bClipOver)
        if (lcl_SelHasAttrib( rDoc, nStartCol,nStartRow, nUndoEndCol,nUndoEndRow, aFilteredMark, HasAttrFlags::Overlapped ))
        {       // "Cell merge not possible if cells already merged"
            ScDocAttrIterator aIter( rDoc, nStartTab, nStartCol, nStartRow, nUndoEndCol, nUndoEndRow );
            const ScPatternAttr* pPattern = nullptr;
            SCCOL nCol = -1;
            SCROW nRow1 = -1;
            SCROW nRow2 = -1;
            while ( ( pPattern = aIter.GetNext( nCol, nRow1, nRow2 ) ) != nullptr )
            {
                const ScMergeAttr& rMergeFlag = pPattern->GetItem(ATTR_MERGE);
                const ScMergeFlagAttr& rMergeFlagAttr = pPattern->GetItem(ATTR_MERGE_FLAG);
                if (rMergeFlag.IsMerged() || rMergeFlagAttr.IsOverlapped())
                {
                    ScRange aRange(nCol, nRow1, nStartTab);
                    rDoc.ExtendOverlapped(aRange);
                    rDoc.ExtendMerge(aRange, true);
                    rDocFunc.UnmergeCells(aRange, bRecord, nullptr /*TODO: should pass combined UndoDoc if bRecord*/);
                }
            }
        }

    if ( !bCutMode )
    {
        ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
        if ( pChangeTrack )
            pChangeTrack->ResetLastCut();   // no more cut-mode
    }

    bool bColInfo = ( nStartRow==0 && nEndRow==rDoc.MaxRow() );
    bool bRowInfo = ( nStartCol==0 && nEndCol==rDoc.MaxCol() );

    ScDocumentUniquePtr pUndoDoc;
    std::unique_ptr<ScDocument> pRefUndoDoc;
    std::unique_ptr<ScRefUndoData> pUndoData;

    if ( bRecord )
    {
        pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
        pUndoDoc->InitUndoSelected( rDoc, aFilteredMark, bColInfo, bRowInfo );

        // all sheets - CopyToDocument skips those that don't exist in pUndoDoc
        SCTAB nTabCount = rDoc.GetTableCount();
        rDoc.CopyToDocument( nStartCol, nStartRow, 0, nUndoEndCol, nUndoEndRow, nTabCount-1,
                              nUndoFlags, false, *pUndoDoc );

        if ( bCutMode )
        {
            pRefUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
            pRefUndoDoc->InitUndo( rDoc, 0, nTabCount-1 );

            pUndoData.reset(new ScRefUndoData( rDoc ));
        }
    }

    const bool bSingleCellBefore = nStartCol == nEndCol &&
                                   nStartRow == nEndRow &&
                                   nStartTab == nEndTab;
    tools::Long nBeforeHint(bSingleCellBefore ? pDocSh->GetTwipWidthHint(ScAddress(nStartCol, nStartRow, nStartTab)) : -1);

    sal_uInt16 nExtFlags = 0;
    pDocSh->UpdatePaintExt( nExtFlags, nStartCol, nStartRow, nStartTab,
                                       nEndCol,   nEndRow,   nEndTab );     // content before the change

    if (GetViewData().IsActive())
    {
        DoneBlockMode();
        InitOwnBlockMode( aUserRange );
    }
    rMark.SetMarkArea( aUserRange );
    MarkDataChanged();

        //  copy from clipboard
        //  save original data in case of calculation

    ScDocumentUniquePtr pMixDoc;
    if (nFunction != ScPasteFunc::NONE)
    {
        bSkipEmptyCells = false;
        if ( nFlags & InsertDeleteFlags::CONTENTS )
        {
            pMixDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
            pMixDoc->InitUndo( rDoc, nStartTab, nEndTab );
            rDoc.CopyToDocument(nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab,
                                 InsertDeleteFlags::CONTENTS, false, *pMixDoc);
        }
    }

    /*  Make draw layer and start drawing undo.
        - Needed before AdjustBlockHeight to track moved drawing objects.
        - Needed before rDoc.CopyFromClip to track inserted note caption objects.
     */
    if ( bPasteDraw )
        pDocSh->MakeDrawLayer();
    if ( bRecord )
        rDoc.BeginDrawUndo();

    InsertDeleteFlags nNoObjFlags = nFlags & ~InsertDeleteFlags::OBJECTS;
    if (!bAsLink)
    {
        //  copy normally (original range)
        rDoc.CopyFromClip( aUserRange, aFilteredMark, nNoObjFlags,
                pRefUndoDoc.get(), pClipDoc, true, false, bIncludeFiltered,
                bSkipEmptyCells, (bMarkIsFiltered ? &aRangeList : nullptr) );

        // adapt refs manually in case of transpose
        if ( bTranspose && bCutMode && (nFlags & InsertDeleteFlags::CONTENTS) )
            rDoc.UpdateTranspose( aUserRange.aStart, pOrigClipDoc, aFilteredMark, pRefUndoDoc.get() );
    }
    else if (!bTranspose)
    {
        //  copy with bAsLink=TRUE
        rDoc.CopyFromClip( aUserRange, aFilteredMark, nNoObjFlags, pRefUndoDoc.get(), pClipDoc,
                                true, true, bIncludeFiltered, bSkipEmptyCells );
    }
    else
    {
        //  copy all content (TransClipDoc contains only formula)
        rDoc.CopyFromClip( aUserRange, aFilteredMark, nContFlags, pRefUndoDoc.get(), pClipDoc );
    }

    // skipped rows and merged cells don't mix
    if ( !bIncludeFiltered && pClipDoc->HasClipFilteredRows() )
        rDocFunc.UnmergeCells( aUserRange, false, nullptr );

    rDoc.ExtendMergeSel( nStartCol, nStartRow, nEndCol, nEndRow, aFilteredMark, true );    // refresh
                                                                                    // new range

    if ( pMixDoc )              // calculate with original data?
    {
        rDoc.MixDocument( aUserRange, nFunction, bSkipEmptyCells, *pMixDoc );
    }
    pMixDoc.reset();

    bool IsRangeWidthChanged = nRangeWidth != GetViewData().GetDocShell()->GetDocument().GetColWidth(nStartCol,nEndCol,nStartTab);
    AdjustBlockHeight(true, nullptr, IsRangeWidthChanged );            // update row heights before pasting objects

    ::std::vector< OUString > aExcludedChartNames;
    SdrPage* pPage = nullptr;

    if ( nFlags & InsertDeleteFlags::OBJECTS )
    {
        ScDrawView* pScDrawView = GetScDrawView();
        SdrModel* pModel = ( pScDrawView ? &pScDrawView->GetModel() : nullptr );
        pPage = ( pModel ? pModel->GetPage( static_cast< sal_uInt16 >( nStartTab ) ) : nullptr );
        if ( pPage )
        {
            ScChartHelper::GetChartNames( aExcludedChartNames, pPage );
        }

        //  Paste the drawing objects after the row heights have been updated.

        rDoc.CopyFromClip( aUserRange, aFilteredMark, InsertDeleteFlags::OBJECTS, pRefUndoDoc.get(), pClipDoc,
                                true, false, bIncludeFiltered );
    }

    pDocSh->UpdatePaintExt( nExtFlags, nStartCol, nStartRow, nStartTab,
                                       nEndCol,   nEndRow,   nEndTab );     // content after the change

        //  if necessary, delete autofilter-heads
    if (bCutMode)
        if (rDoc.RefreshAutoFilter( nClipStartX,nClipStartY, nClipStartX+nClipSizeX,
                                        nClipStartY+nClipSizeY, nStartTab ))
        {
            pDocSh->PostPaint(
                ScRange(nClipStartX, nClipStartY, nStartTab, nClipStartX+nClipSizeX, nClipStartY, nStartTab),
                PaintPartFlags::Grid );
        }

    //!     remove block-range on RefUndoDoc !!!

    if ( bRecord )
    {
        ScDocumentUniquePtr pRedoDoc;
        // copy redo data after appearance of the first undo
        // don't create Redo-Doc without RefUndoDoc

        if (pRefUndoDoc)
        {
            pRedoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
            pRedoDoc->InitUndo( rDoc, nStartTab, nEndTab, bColInfo, bRowInfo );

            //      move adapted refs to Redo-Doc

            SCTAB nTabCount = rDoc.GetTableCount();
            pRedoDoc->AddUndoTab( 0, nTabCount-1 );
            rDoc.CopyUpdated( pRefUndoDoc.get(), pRedoDoc.get() );

            //      move old refs to Undo-Doc

            //      not charts?
            pUndoDoc->AddUndoTab( 0, nTabCount-1 );
            pRefUndoDoc->DeleteArea( nStartCol, nStartRow, nEndCol, nEndRow, aFilteredMark, InsertDeleteFlags::ALL );
            pRefUndoDoc->CopyToDocument( 0,0,0, pUndoDoc->MaxCol(), pUndoDoc->MaxRow(), nTabCount-1,
                                            InsertDeleteFlags::FORMULA, false, *pUndoDoc );
            pRefUndoDoc.reset();
        }

        //  DeleteUnchanged for pUndoData is in ScUndoPaste ctor,
        //  UndoData for redo is made during first undo

        ScUndoPasteOptions aOptions;            // store options for repeat
        aOptions.nFunction  = nFunction;
        aOptions.bSkipEmptyCells = bSkipEmptyCells;
        aOptions.bTranspose = bTranspose;
        aOptions.bAsLink    = bAsLink;
        aOptions.eMoveMode  = eMoveMode;

        std::unique_ptr<SfxUndoAction> pUndo(new ScUndoPaste(
            *pDocSh, ScRange(nStartCol, nStartRow, nStartTab, nUndoEndCol, nUndoEndRow, nEndTab),
            aFilteredMark, std::move(pUndoDoc), std::move(pRedoDoc), nFlags | nUndoFlags, std::move(pUndoData),
            false, &aOptions ));     // false = Redo data not yet copied

        if ( bInsertCells )
        {
            //  Merge the paste undo action into the insert action.
            //  Use ScUndoWrapper so the ScUndoPaste pointer can be stored in the insert action.

            pUndoMgr->AddUndoAction( std::make_unique<ScUndoWrapper>( std::move(pUndo) ), true );
        }
        else
            pUndoMgr->AddUndoAction( std::move(pUndo) );
        pUndoMgr->LeaveListAction();
    }

    PaintPartFlags nPaint = PaintPartFlags::Grid;
    if (bColInfo)
    {
        nPaint |= PaintPartFlags::Top;
        nUndoEndCol = rDoc.MaxCol();               // just for drawing !
    }
    if (bRowInfo)
    {
        nPaint |= PaintPartFlags::Left;
        nUndoEndRow = rDoc.MaxRow();               // just for drawing !
    }

    tools::Long nMaxWidthAffectedHint = -1;
    const bool bSingleCellAfter = nStartCol == nUndoEndCol &&
                                  nStartRow == nUndoEndRow &&
                                  nStartTab == nEndTab;
    if (bSingleCellBefore && bSingleCellAfter)
    {
        tools::Long nAfterHint(pDocSh->GetTwipWidthHint(ScAddress(nStartCol, nStartRow, nStartTab)));
        nMaxWidthAffectedHint = std::max(nBeforeHint, nAfterHint);
    }

    pDocSh->PostPaint(
        ScRange(nStartCol, nStartRow, nStartTab, nUndoEndCol, nUndoEndRow, nEndTab),
        nPaint, nExtFlags, nMaxWidthAffectedHint);
    // AdjustBlockHeight has already been called above

    aModificator.SetDocumentModified();
    PostPasteFromClip(aUserRange, rMark);

    if ( nFlags & InsertDeleteFlags::OBJECTS )
    {
        ScModelObj* pModelObj = pDocSh->GetModel();
        if ( pPage && pModelObj )
        {
            bool bSameDoc = ( rClipParam.getSourceDocID() == rDoc.GetDocumentID() );
            const ScRangeListVector& rProtectedChartRangesVector( rClipParam.maProtectedChartRangesVector );
            ScChartHelper::CreateProtectedChartListenersAndNotify( rDoc, pPage, pModelObj, nStartTab,
                rProtectedChartRangesVector, aExcludedChartNames, bSameDoc );
        }
    }
    OUString aStartAddress =  aMarkRange.aStart.GetColRowString();
    OUString aEndAddress = aMarkRange.aEnd.GetColRowString();
    collectUIInformation({{"RANGE", aStartAddress + ":" + aEndAddress}}, u"PASTE"_ustr);
    return true;
}

bool ScViewFunc::PasteMultiRangesFromClip(InsertDeleteFlags nFlags, ScDocument* pClipDoc,
                                          ScPasteFunc nFunction, bool bSkipEmptyCells,
                                          bool bTranspose, bool bAsLink,
                                          bool bAllowDialogs, InsCellCmd eMoveMode,
                                          InsertDeleteFlags nUndoFlags)
{
    ScViewData& rViewData = GetViewData();
    ScDocument& rDoc = rViewData.GetDocument();
    ScDocShell* pDocSh = rViewData.GetDocShell();
    ScMarkData aMark(rViewData.GetMarkData());
    const ScAddress aCurPos = rViewData.GetCurPos();
    ScClipParam& rClipParam = pClipDoc->GetClipParam();
    SCCOL nColSize = rClipParam.getPasteColSize();
    SCROW nRowSize = rClipParam.getPasteRowSize(*pClipDoc, /*bIncludeFiltered*/false);

    if (bTranspose)
    {
        if (static_cast<SCROW>(aCurPos.Col()) + nRowSize-1 > static_cast<SCROW>(pClipDoc->MaxCol()))
        {
            ErrorMessage(STR_PASTE_FULL);
            return false;
        }

        ScDocumentUniquePtr pTransClip(new ScDocument(SCDOCMODE_CLIP));
        pClipDoc->TransposeClip(pTransClip.get(), nFlags, bAsLink, /*bIncludeFiltered*/false);
        pClipDoc = pTransClip.release();
        SCCOL nTempColSize = nColSize;
        nColSize = static_cast<SCCOL>(nRowSize);
        nRowSize = static_cast<SCROW>(nTempColSize);
    }

    if (!rDoc.ValidCol(aCurPos.Col()+nColSize-1) || !rDoc.ValidRow(aCurPos.Row()+nRowSize-1))
    {
        ErrorMessage(STR_PASTE_FULL);
        return false;
    }

    // Determine the first and last selected sheet numbers.
    SCTAB nTab1 = aMark.GetFirstSelected();
    SCTAB nTab2 = aMark.GetLastSelected();

    ScDocShellModificator aModificator(*pDocSh);

    // For multi-selection paste, we don't support cell duplication for larger
    // destination range.  In case the destination is marked, we reset it to
    // the clip size.
    ScRange aMarkedRange(aCurPos.Col(), aCurPos.Row(), nTab1,
                         aCurPos.Col()+nColSize-1, aCurPos.Row()+nRowSize-1, nTab2);

    // Extend the marked range to account for filtered rows in the destination
    // area.
    if (ScViewUtil::HasFiltered(aMarkedRange, rDoc))
    {
        if (!ScViewUtil::FitToUnfilteredRows(aMarkedRange, rDoc, nRowSize))
            return false;
    }

    bool bAskIfNotEmpty =
        bAllowDialogs && (nFlags & InsertDeleteFlags::CONTENTS) &&
        nFunction == ScPasteFunc::NONE && ScModule::get()->GetInputOptions().GetReplaceCellsWarn();

    if (bAskIfNotEmpty)
    {
        ScRangeList aTestRanges(aMarkedRange);
        if (!checkDestRangeForOverwrite(nFlags, aTestRanges, rDoc, aMark, GetViewData().GetDialogParent()))
            return false;
    }

    aMark.SetMarkArea(aMarkedRange);
    MarkRange(aMarkedRange);

    bool bInsertCells = (eMoveMode != INS_NONE);
    if (bInsertCells)
    {
        if (!InsertCells(eMoveMode, rDoc.IsUndoEnabled(), true))
            return false;
    }

    // TODO: position this call better for performance.
    ResetAutoSpellForContentChange();

    bool bRowInfo = ( aMarkedRange.aStart.Col()==0 && aMarkedRange.aEnd.Col()==pClipDoc->MaxCol() );
    ScDocumentUniquePtr pUndoDoc;
    if (rDoc.IsUndoEnabled())
    {
        pUndoDoc.reset(new ScDocument(SCDOCMODE_UNDO));
        pUndoDoc->InitUndoSelected(rDoc, aMark, false, bRowInfo);
        rDoc.CopyToDocument(aMarkedRange, nUndoFlags, false, *pUndoDoc, &aMark);
    }

    ScDocumentUniquePtr pMixDoc;
    if ( bSkipEmptyCells || nFunction != ScPasteFunc::NONE)
    {
        if ( nFlags & InsertDeleteFlags::CONTENTS )
        {
            pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO));
            pMixDoc->InitUndoSelected(rDoc, aMark);
            rDoc.CopyToDocument(aMarkedRange, InsertDeleteFlags::CONTENTS, false, *pMixDoc, &aMark);
        }
    }

    /*  Make draw layer and start drawing undo.
        - Needed before AdjustBlockHeight to track moved drawing objects.
        - Needed before rDoc.CopyFromClip to track inserted note caption objects.
     */
    if (nFlags & InsertDeleteFlags::OBJECTS)
        pDocSh->MakeDrawLayer();
    if (rDoc.IsUndoEnabled())
        rDoc.BeginDrawUndo();

    InsertDeleteFlags nCopyFlags = nFlags & ~InsertDeleteFlags::OBJECTS;
    // in case of transpose, links were added in TransposeClip()
    if (bAsLink && bTranspose)
        nCopyFlags |= InsertDeleteFlags::FORMULA;
    rDoc.CopyMultiRangeFromClip(aCurPos, aMark, nCopyFlags, pClipDoc, true, bAsLink && !bTranspose,
                                /*bIncludeFiltered*/false, bSkipEmptyCells);

    if (pMixDoc)
        rDoc.MixDocument(aMarkedRange, nFunction, bSkipEmptyCells, *pMixDoc);

    AdjustBlockHeight();            // update row heights before pasting objects

    if (nFlags & InsertDeleteFlags::OBJECTS)
    {
        //  Paste the drawing objects after the row heights have been updated.
        rDoc.CopyMultiRangeFromClip(aCurPos, aMark, InsertDeleteFlags::OBJECTS, pClipDoc, true,
                                    false, /*bIncludeFiltered*/false, true);
    }

    if (bRowInfo)
        pDocSh->PostPaint(aMarkedRange.aStart.Col(), aMarkedRange.aStart.Row(), nTab1, pClipDoc->MaxCol(), pClipDoc->MaxRow(), nTab1, PaintPartFlags::Grid|PaintPartFlags::Left);
    else
    {
        ScRange aTmp = aMarkedRange;
        aTmp.aStart.SetTab(nTab1);
        aTmp.aEnd.SetTab(nTab1);
        pDocSh->PostPaint(aTmp, PaintPartFlags::Grid);
    }

    if (rDoc.IsUndoEnabled())
    {
        SfxUndoManager* pUndoMgr = pDocSh->GetUndoManager();
        OUString aUndo = ScResId(
            pClipDoc->IsCutMode() ? STR_UNDO_CUT : STR_UNDO_COPY);
        pUndoMgr->EnterListAction(aUndo, aUndo, 0, GetViewData().GetViewShell()->GetViewShellId());

        ScUndoPasteOptions aOptions;            // store options for repeat
        aOptions.nFunction  = nFunction;
        aOptions.bSkipEmptyCells = bSkipEmptyCells;
        aOptions.bTranspose = bTranspose;
        aOptions.bAsLink    = bAsLink;
        aOptions.eMoveMode  = eMoveMode;

        std::unique_ptr<ScUndoPaste> pUndo(new ScUndoPaste(*pDocSh,
            aMarkedRange, aMark, std::move(pUndoDoc), nullptr, nFlags|nUndoFlags, nullptr, false, &aOptions));

        if (bInsertCells)
            pUndoMgr->AddUndoAction(std::make_unique<ScUndoWrapper>(std::move(pUndo)), true);
        else
            pUndoMgr->AddUndoAction(std::move(pUndo));

        pUndoMgr->LeaveListAction();
    }

    aModificator.SetDocumentModified();
    PostPasteFromClip(aMarkedRange, aMark);
    return true;
}

bool ScViewFunc::PasteFromClipToMultiRanges(
    InsertDeleteFlags nFlags, ScDocument* pClipDoc, ScPasteFunc nFunction,
    bool bSkipEmptyCells, bool bTranspose, bool bAsLink, bool bAllowDialogs,
    InsCellCmd eMoveMode, InsertDeleteFlags nUndoFlags )
{
    if (bTranspose)
    {
        // We don't allow transpose for this yet.
        ErrorMessage(STR_MSSG_PASTEFROMCLIP_0);
        return false;
    }

    if (eMoveMode != INS_NONE)
    {
        // We don't allow insertion mode either.  Too complicated.
        ErrorMessage(STR_MSSG_PASTEFROMCLIP_0);
        return false;
    }

    ScViewData& rViewData = GetViewData();
    ScClipParam& rClipParam = pClipDoc->GetClipParam();
    if (rClipParam.mbCutMode)
    {
        // No cut and paste with this, please.
        ErrorMessage(STR_MSSG_PASTEFROMCLIP_0);
        return false;
    }

    const ScAddress aCurPos = rViewData.GetCurPos();
    ScDocument& rDoc = rViewData.GetDocument();

    ScRange aSrcRange = rClipParam.getWholeRange();
    SCROW nRowSize = aSrcRange.aEnd.Row() - aSrcRange.aStart.Row() + 1;
    SCCOL nColSize = aSrcRange.aEnd.Col() - aSrcRange.aStart.Col() + 1;

    if (!rDoc.ValidCol(aCurPos.Col()+nColSize-1) || !rDoc.ValidRow(aCurPos.Row()+nRowSize-1))
    {
        ErrorMessage(STR_PASTE_FULL);
        return false;
    }

    ScMarkData aMark(rViewData.GetMarkData());

    ScRangeList aRanges;
    aMark.MarkToSimple();
    aMark.FillRangeListWithMarks(&aRanges, false);
    if (!ScClipUtil::CheckDestRanges(rDoc, nColSize, nRowSize, aMark, aRanges))
    {
        ErrorMessage(STR_MSSG_PASTEFROMCLIP_0);
        return false;
    }

    ScDocShell* pDocSh = rViewData.GetDocShell();

    ScDocShellModificator aModificator(*pDocSh);

    bool bAskIfNotEmpty =
        bAllowDialogs && (nFlags & InsertDeleteFlags::CONTENTS) &&
        nFunction == ScPasteFunc::NONE && ScModule::get()->GetInputOptions().GetReplaceCellsWarn();

    if (bAskIfNotEmpty)
    {
        if (!checkDestRangeForOverwrite(nFlags, aRanges, rDoc, aMark, GetViewData().GetDialogParent()))
            return false;
    }

    // TODO: position this call better for performance.
    ResetAutoSpellForContentChange();

    ScDocumentUniquePtr pUndoDoc;
    if (rDoc.IsUndoEnabled())
    {
        pUndoDoc.reset(new ScDocument(SCDOCMODE_UNDO));
        pUndoDoc->InitUndoSelected(rDoc, aMark);
        for (size_t i = 0, n = aRanges.size(); i < n; ++i)
        {
            rDoc.CopyToDocument(
                aRanges[i], nUndoFlags, false, *pUndoDoc, &aMark);
        }
    }

    ScDocumentUniquePtr pMixDoc;
    if (bSkipEmptyCells || nFunction != ScPasteFunc::NONE)
    {
        if (nFlags & InsertDeleteFlags::CONTENTS)
        {
            pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO));
            pMixDoc->InitUndoSelected(rDoc, aMark);
            for (size_t i = 0, n = aRanges.size(); i < n; ++i)
            {
                rDoc.CopyToDocument(
                    aRanges[i], InsertDeleteFlags::CONTENTS, false, *pMixDoc, &aMark);
            }
        }
    }

    if (nFlags & InsertDeleteFlags::OBJECTS)
        pDocSh->MakeDrawLayer();
    if (rDoc.IsUndoEnabled())
        rDoc.BeginDrawUndo();

    // First, paste everything but the drawing objects.
    for (size_t i = 0, n = aRanges.size(); i < n; ++i)
    {
        rDoc.CopyFromClip(
            aRanges[i], aMark, (nFlags & ~InsertDeleteFlags::OBJECTS), nullptr, pClipDoc,
            false, false, true, bSkipEmptyCells);
    }

    if (pMixDoc)
    {
        for (size_t i = 0, n = aRanges.size(); i < n; ++i)
            rDoc.MixDocument(aRanges[i], nFunction, bSkipEmptyCells, *pMixDoc);
    }

    AdjustBlockHeight();            // update row heights before pasting objects

    // Then paste the objects.
    if (nFlags & InsertDeleteFlags::OBJECTS)
    {
        for (size_t i = 0, n = aRanges.size(); i < n; ++i)
        {
            rDoc.CopyFromClip(
                aRanges[i], aMark, InsertDeleteFlags::OBJECTS, nullptr, pClipDoc,
                false, false, true, bSkipEmptyCells);
        }
    }

    // Refresh the range that includes all pasted ranges.  We only need to
    // refresh the current sheet.
    PaintPartFlags nPaint = PaintPartFlags::Grid;
    bool bRowInfo = (aSrcRange.aStart.Col()==0 &&  aSrcRange.aEnd.Col()==pClipDoc->MaxCol());
    if (bRowInfo)
        nPaint |= PaintPartFlags::Left;
    pDocSh->PostPaint(aRanges, nPaint);

    if (rDoc.IsUndoEnabled())
    {
        SfxUndoManager* pUndoMgr = pDocSh->GetUndoManager();
        OUString aUndo = ScResId(
            pClipDoc->IsCutMode() ? STR_UNDO_CUT : STR_UNDO_COPY);
        pUndoMgr->EnterListAction(aUndo, aUndo, 0, GetViewData().GetViewShell()->GetViewShellId());

        ScUndoPasteOptions aOptions;            // store options for repeat
        aOptions.nFunction  = nFunction;
        aOptions.bSkipEmptyCells = bSkipEmptyCells;
        aOptions.bTranspose = bTranspose;
        aOptions.bAsLink    = bAsLink;
        aOptions.eMoveMode  = eMoveMode;


        pUndoMgr->AddUndoAction(
            std::make_unique<ScUndoPaste>(
                *pDocSh, aRanges, aMark, std::move(pUndoDoc), nullptr, nFlags|nUndoFlags, nullptr, false, &aOptions));
        pUndoMgr->LeaveListAction();
    }

    aModificator.SetDocumentModified();
    PostPasteFromClip(aRanges, aMark);

    return false;
}

void ScViewFunc::PostPasteFromClip(const ScRangeList& rPasteRanges, const ScMarkData& rMark)
{
    ScViewData& rViewData = GetViewData();
    ScDocShell* pDocSh = rViewData.GetDocShell();
    pDocSh->UpdateOle(rViewData);

    SelectionChanged(true);

    ScModelObj* pModelObj = pDocSh->GetModel();

    ScRangeList aChangeRanges;
    for (size_t i = 0, n = rPasteRanges.size(); i < n; ++i)
    {
        const ScRange& r = rPasteRanges[i];
        for (const auto& rTab : rMark)
        {
            ScRange aChangeRange(r);
            aChangeRange.aStart.SetTab(rTab);
            aChangeRange.aEnd.SetTab(rTab);
            aChangeRanges.push_back(aChangeRange);
        }
    }

    if (HelperNotifyChanges::getMustPropagateChangesModel(pModelObj))
        HelperNotifyChanges::Notify(*pModelObj, aChangeRanges, u"paste"_ustr);
    else if (pModelObj)
        HelperNotifyChanges::Notify(*pModelObj, aChangeRanges, u"data-area-invalidate"_ustr);
}

//      D R A G   A N D   D R O P

//  inside the doc

bool ScViewFunc::MoveBlockTo( const ScRange& rSource, const ScAddress& rDestPos,
                                bool bCut )
{
    ScDocShell* pDocSh = GetViewData().GetDocShell();
    HideAllCursors();

    ResetAutoSpellForContentChange();

    bool bSuccess = true;
    SCTAB nDestTab = rDestPos.Tab();
    const ScMarkData& rMark = GetViewData().GetMarkData();
    if ( rSource.aStart.Tab() == nDestTab && rSource.aEnd.Tab() == nDestTab && rMark.GetSelectCount() > 1 )
    {
        //  moving within one table and several tables selected -> apply to all selected tables

        OUString aUndo = ScResId( bCut ? STR_UNDO_MOVE : STR_UNDO_COPY );
        pDocSh->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, GetViewData().GetViewShell()->GetViewShellId() );

        //  collect ranges of consecutive selected tables

        ScRange aLocalSource = rSource;
        ScAddress aLocalDest = rDestPos;
        SCTAB nTabCount = pDocSh->GetDocument().GetTableCount();
        SCTAB nStartTab = 0;
        while ( nStartTab < nTabCount && bSuccess )
        {
            while ( nStartTab < nTabCount && !rMark.GetTableSelect(nStartTab) )
                ++nStartTab;
            if ( nStartTab < nTabCount )
            {
                SCTAB nEndTab = nStartTab;
                while ( nEndTab+1 < nTabCount && rMark.GetTableSelect(nEndTab+1) )
                    ++nEndTab;

                aLocalSource.aStart.SetTab( nStartTab );
                aLocalSource.aEnd.SetTab( nEndTab );
                aLocalDest.SetTab( nStartTab );

                bSuccess = pDocSh->GetDocFunc().MoveBlock(
                                aLocalSource, aLocalDest, bCut, true/*bRecord*/, true/*bPaint*/, true/*bApi*/ );

                nStartTab = nEndTab + 1;
            }
        }

        pDocSh->GetUndoManager()->LeaveListAction();
    }
    else
    {
        //  move the block as specified
        bSuccess = pDocSh->GetDocFunc().MoveBlock(
                                rSource, rDestPos, bCut, true/*bRecord*/, true/*bPaint*/, true/*bApi*/ );
    }

    ShowAllCursors();
    if (bSuccess)
    {
        //   mark destination range
        ScAddress aDestEnd(
                    rDestPos.Col() + rSource.aEnd.Col() - rSource.aStart.Col(),
                    rDestPos.Row() + rSource.aEnd.Row() - rSource.aStart.Row(),
                    nDestTab );

        bool bIncludeFiltered = bCut;
        if ( !bIncludeFiltered )
        {
            // find number of non-filtered rows
            SCROW nPastedCount = pDocSh->GetDocument().CountNonFilteredRows(
                rSource.aStart.Row(), rSource.aEnd.Row(), rSource.aStart.Tab());

            if ( nPastedCount == 0 )
                nPastedCount = 1;
            aDestEnd.SetRow( rDestPos.Row() + nPastedCount - 1 );
        }

        MarkRange( ScRange( rDestPos, aDestEnd ), false );          //! sal_False ???

        pDocSh->UpdateOle(GetViewData());
        SelectionChanged();
    }
    return bSuccess;
}

//  link inside the doc

bool ScViewFunc::LinkBlock( const ScRange& rSource, const ScAddress& rDestPos )
{
    //  check overlapping

    if ( rSource.aStart.Tab() == rDestPos.Tab() )
    {
        SCCOL nDestEndCol = rDestPos.Col() + ( rSource.aEnd.Col() - rSource.aStart.Col() );
        SCROW nDestEndRow = rDestPos.Row() + ( rSource.aEnd.Row() - rSource.aStart.Row() );

        if ( rSource.aStart.Col() <= nDestEndCol && rDestPos.Col() <= rSource.aEnd.Col() &&
             rSource.aStart.Row() <= nDestEndRow && rDestPos.Row() <= rSource.aEnd.Row() )
        {
            return false;
        }
    }

    //  run with paste

    ScDocument& rDoc = GetViewData().GetDocument();
    ScDocumentUniquePtr pClipDoc(new ScDocument( SCDOCMODE_CLIP ));
    rDoc.CopyTabToClip( rSource.aStart.Col(), rSource.aStart.Row(),
                        rSource.aEnd.Col(), rSource.aEnd.Row(),
                        rSource.aStart.Tab(), pClipDoc.get() );

    //  mark destination area (set cursor, no marks)

    if ( GetViewData().CurrentTabForData() != rDestPos.Tab() )
        SetTabNo( rDestPos.Tab() );

    MoveCursorAbs( rDestPos.Col(), rDestPos.Row(), SC_FOLLOW_NONE, false, false );

    //  Paste

    PasteFromClip( InsertDeleteFlags::ALL, pClipDoc.get(), ScPasteFunc::NONE, false, false, true );       // as a link

    return true;
}

void ScViewFunc::DataFormPutData( SCROW nCurrentRow ,
                                  SCROW nStartRow , SCCOL nStartCol ,
                                  SCROW nEndRow , SCCOL nEndCol ,
                                  std::vector<std::unique_ptr<ScDataFormFragment>>& rEdits,
                                  sal_uInt16 aColLength )
{
    ScDocument& rDoc = GetViewData().GetDocument();
    ScDocShell* pDocSh = GetViewData().GetDocShell();
    ScMarkData& rMark = GetViewData().GetMarkData();
    ScDocShellModificator aModificator( *pDocSh );
    SfxUndoManager* pUndoMgr = pDocSh->GetUndoManager();

    const bool bRecord( rDoc.IsUndoEnabled());
    ScDocumentUniquePtr pUndoDoc;
    ScDocumentUniquePtr pRedoDoc;
    std::unique_ptr<ScRefUndoData> pUndoData;
    SCTAB nTab = GetViewData().CurrentTabForData();
    SCTAB nStartTab = nTab;
    SCTAB nEndTab = nTab;

    {
            ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
            if ( pChangeTrack )
                    pChangeTrack->ResetLastCut();   // no more cut-mode
    }
    ScRange aUserRange( nStartCol, nCurrentRow, nStartTab, nEndCol, nCurrentRow, nEndTab );
    bool bColInfo = ( nStartRow==0 && nEndRow==rDoc.MaxRow() );
    bool bRowInfo = ( nStartCol==0 && nEndCol==rDoc.MaxCol() );
    SCCOL nUndoEndCol = nStartCol+aColLength-1;
    SCROW nUndoEndRow = nCurrentRow;

    if ( bRecord )
    {
        pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
        pUndoDoc->InitUndoSelected( rDoc , rMark , bColInfo , bRowInfo );
        rDoc.CopyToDocument( aUserRange , InsertDeleteFlags::VALUE , false, *pUndoDoc );
    }
    sal_uInt16 nExtFlags = 0;
    pDocSh->UpdatePaintExt( nExtFlags, nStartCol, nStartRow, nStartTab , nEndCol, nEndRow, nEndTab ); // content before the change
    rDoc.BeginDrawUndo();

    for(sal_uInt16 i = 0; i < aColLength; i++)
    {
        if (rEdits[i] != nullptr)
        {
            OUString aFieldName = rEdits[i]->m_xEdit->get_text();
            rDoc.SetString( nStartCol + i, nCurrentRow, nTab, aFieldName );
        }
    }
    pDocSh->UpdatePaintExt( nExtFlags, nStartCol, nCurrentRow, nStartTab, nEndCol, nCurrentRow, nEndTab );  // content after the change
    std::unique_ptr<SfxUndoAction> pUndo( new ScUndoDataForm( *pDocSh,
                                               nStartCol, nCurrentRow, nStartTab,
                                               nUndoEndCol, nUndoEndRow, nEndTab, rMark,
                                               std::move(pUndoDoc), std::move(pRedoDoc),
                                               std::move(pUndoData) ) );
    pUndoMgr->AddUndoAction( std::make_unique<ScUndoWrapper>( std::move(pUndo) ), true );

    PaintPartFlags nPaint = PaintPartFlags::Grid;
    if (bColInfo)
    {
            nPaint |= PaintPartFlags::Top;
            nUndoEndCol = rDoc.MaxCol();                           // just for drawing !
    }
    if (bRowInfo)
    {
            nPaint |= PaintPartFlags::Left;
            nUndoEndRow = rDoc.MaxRow();                           // just for drawing !
    }

    pDocSh->PostPaint(
        ScRange(nStartCol, nCurrentRow, nStartTab, nUndoEndCol, nUndoEndRow, nEndTab),
        nPaint, nExtFlags);
    pDocSh->UpdateOle(GetViewData());
}

void ScViewFunc::SheetViewChanged()
{
    ScViewData& rViewData = GetViewData();
    ScDocShell* pDocSh = rViewData.GetDocShell();
    ScDocument& rDocument = rViewData.GetDocument();
    pDocSh->PostPaint(0, 0, 0, rDocument.MaxCol(), rDocument.MaxRow(), MAXTAB, PaintPartFlags::All);

    if (ScTabViewShell* pViewShell = GetViewData().GetViewShell())
    {
        ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>(pViewShell->GetCurrentDocument());
        SfxLokHelper::notifyViewRenderState(pViewShell, pModel);
        pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_INVALIDATE_HEADER, "all"_ostr);
        pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_INVALIDATE_SHEET_GEOMETRY, "all"_ostr);
    }
    SfxBindings& rBindings = rViewData.GetBindings();
    rBindings.Invalidate(FID_CURRENT_SHEET_VIEW);
}

void ScViewFunc::MakeNewSheetView()
{
    SCTAB nTab = GetViewData().GetTabNumber();
    ScDocument& rDocument = GetViewData().GetDocument();

    auto[nSheetViewID, nSheetViewTab] = rDocument.CreateNewSheetView(nTab);

    if (nSheetViewID == sc::InvalidSheetViewID)
    {
        SAL_WARN("sc", "Sheet view couldn't be created");
        return;
    }

    GetViewData().SetSheetViewID(nSheetViewID);

    GetViewData().GetDocShell()->Broadcast(ScTablesHint(SC_TAB_INSERTED, nSheetViewTab));
    SfxGetpApp()->Broadcast(SfxHint(SfxHintId::ScTablesChanged));

    // Need to make sure we return to the main sheet, to make sure we are not at a different location after inserting
    SetTabNo(nTab);

    SheetViewChanged();
}

void ScViewFunc::RemoveCurrentSheetView()
{
    sc::SheetViewID nSheetViewID = GetViewData().GetSheetViewID();
    if (nSheetViewID == sc::DefaultSheetViewID)
        return;

    ScDocument& rDocument = GetViewData().GetDocument();
    SCTAB nTab = GetViewData().GetTabNumber();
    if (rDocument.IsSheetViewHolder(nTab))
        return;

    auto pSheetManager = rDocument.GetSheetViewManager(nTab);
    if (!pSheetManager)
        return;

    GetViewData().SetSheetViewID(sc::DefaultSheetViewID);

    SCTAB nSheetViewTab = rDocument.GetSheetViewNumber(nTab, nSheetViewID);
    pSheetManager->remove(nSheetViewID);

    GetViewData().GetDocFunc().DeleteTable(nSheetViewTab, true);

    SheetViewChanged();
}

void ScViewFunc::SwitchSheetView(sc::SwitchSheetViewDirection eDirection)
{
    ScDocument& rDocument = GetViewData().GetDocument();
    SCTAB nTab = GetViewData().GetTabNumber();
    auto pSheetManager = rDocument.GetSheetViewManager(nTab);
    sc::SheetViewID nSheetViewID = GetViewData().GetSheetViewID();

    sc::SheetViewID nSwitchSheetViewID = eDirection == sc::SwitchSheetViewDirection::Next
                                            ? pSheetManager->getNextSheetView(nSheetViewID)
                                            : pSheetManager->getPreviousSheetView(nSheetViewID);
    SelectSheetView(nSwitchSheetViewID);
}

void ScViewFunc::ExitSheetView()
{
    SelectSheetView(sc::DefaultSheetViewID);
}

void ScViewFunc::SelectSheetView(sc::SheetViewID nSelectSheetViewID)
{
    SCTAB nTab = GetViewData().GetTabNumber();

    if (GetViewData().GetDocument().IsSheetViewHolder(nTab))
        return;

    sc::SheetViewID nSheetViewID = GetViewData().GetSheetViewID();
    if (nSheetViewID == nSelectSheetViewID)
        return;

    GetViewData().SetSheetViewID(nSelectSheetViewID);

    SheetViewChanged();
}

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