/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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 <editeng/justifyitem.hxx>

#include <cpo/uno/Sequence.hxx>
#include <com/sun/star/embed/XTransactedObject.hpp>

#include <o3tl/unit_conversion.hxx>
#include <osl/diagnose.h>
#include <unotools/tempfile.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <unotools/streamwrap.hxx>
#include <comphelper/fileformat.h>
#include <comphelper/kit.hxx>
#include <comphelper/storagehelper.hxx>
#include <comphelper/servicehelper.hxx>
#include <sot/storage.hxx>
#include <utility>
#include <vcl/gdimtf.hxx>
#include <vcl/jobset.hxx>
#include <vcl/svapp.hxx>
#include <vcl/virdev.hxx>
#include <sfx2/docfile.hxx>

#include <transobj.hxx>
#include <address.hxx>
#include <clipparam.hxx>
#include <patattr.hxx>
#include <cellvalue.hxx>
#include <cellform.hxx>
#include <document.hxx>
#include <viewopti.hxx>
#include <editutil.hxx>
#include <impex.hxx>
#include <formulacell.hxx>
#include <printfun.hxx>
#include <docfunc.hxx>
#include <scmod.hxx>
#include <dragdata.hxx>
#include <sortparam.hxx>
#include <tabvwsh.hxx>

#include <editeng/paperinf.hxx>
#include <editeng/sizeitem.hxx>
#include <formula/errorcodes.hxx>
#include <docsh.hxx>
#include <markdata.hxx>
#include <stlpool.hxx>
#include <viewdata.hxx>
#include <dociter.hxx>
#include <cellsuno.hxx>
#include <stringutil.hxx>
#include <formulaiter.hxx>

using namespace com::sun::star;

constexpr sal_uInt32 SCTRANS_TYPE_IMPEX              = 1;
constexpr sal_uInt32 SCTRANS_TYPE_EDIT_RTF           = 2;
constexpr sal_uInt32 SCTRANS_TYPE_EDIT_BIN           = 3;
constexpr sal_uInt32 SCTRANS_TYPE_EMBOBJ             = 4;
constexpr sal_uInt32 SCTRANS_TYPE_EDIT_ODF_TEXT_FLAT = 5;

void ScTransferObj::GetAreaSize( const ScDocument& rDoc, SCTAB nTab1, SCTAB nTab2, SCROW& nRow, SCCOL& nCol )
{
    SCCOL nMaxCol = 0;
    SCROW nMaxRow = 0;
    for( SCTAB nTab = nTab1; nTab <= nTab2; nTab++ )
    {
        SCCOL nLastCol = 0;
        SCROW nLastRow = 0;
        // GetPrintArea instead of GetCellArea - include drawing objects
        if( rDoc.GetPrintArea( nTab, nLastCol, nLastRow ) )
        {
            if( nLastCol > nMaxCol )
                nMaxCol = nLastCol;
            if( nLastRow > nMaxRow  )
                nMaxRow = nLastRow;
        }
    }
    nRow = nMaxRow;
    nCol = nMaxCol;
}

void ScTransferObj::PaintToDev( OutputDevice* pDev, ScDocument& rDoc, double nPrintFactor,
                                const ScRange& rBlock )
{
    tools::Rectangle aBound( Point(), pDev->GetOutputSize() );      //! use size from clip area?

    ScViewData aViewData(rDoc);

    aViewData.SetTabNo( rBlock.aEnd.Tab() );
    aViewData.SetScreen( rBlock.aStart.Col(), rBlock.aStart.Row(),
                            rBlock.aEnd.Col(), rBlock.aEnd.Row() );

    ScPrintFunc::DrawToDev( rDoc, pDev, nPrintFactor, aBound, aViewData, false/*bMetaFile*/ );
}

ScTransferObj::ScTransferObj( const std::shared_ptr<ScDocument>& pClipDoc, TransferableObjectDescriptor aDesc ) :
    m_pDoc( pClipDoc ),
    m_nNonFiltered(0),
    m_aObjDesc(std::move( aDesc )),
    m_nDragHandleX( 0 ),
    m_nDragHandleY( 0 ),
    m_nSourceCursorX( m_pDoc->MaxCol() + 1 ),
    m_nSourceCursorY( m_pDoc->MaxRow() + 1 ),
    m_nDragSourceFlags( ScDragSrc::Undefined ),
    m_bDragWasInternal( false ),
    m_bUsedForLink( false ),
    m_bUseInApi( false )
{
    OSL_ENSURE(m_pDoc->IsClipboard(), "wrong document");

    // get aBlock from clipboard doc

    SCCOL nCol1;
    SCROW nRow1;
    SCCOL nCol2;
    SCROW nRow2;
    m_pDoc->GetClipStart( nCol1, nRow1 );
    m_pDoc->GetClipArea( nCol2, nRow2, true );    // real source area - include filtered rows
    nCol2 = sal::static_int_cast<SCCOL>( nCol2 + nCol1 );
    nRow2 = sal::static_int_cast<SCROW>( nRow2 + nRow1 );

    SCCOL nDummy;
    m_pDoc->GetClipArea( nDummy, m_nNonFiltered, false );
    m_bHasFiltered = (m_nNonFiltered < (nRow2 - nRow1));
    ++m_nNonFiltered;     // to get count instead of diff

    SCTAB nTab1=0;
    SCTAB nTab2=0;
    bool bFirst = true;
    for (SCTAB i=0; i< m_pDoc->GetTableCount(); i++)
        if (m_pDoc->HasTable(i))
        {
            if (bFirst)
                nTab1 = i;
            nTab2 = i;
            bFirst = false;
        }
    OSL_ENSURE(!bFirst, "no sheet selected");

    //  only limit to used cells if whole sheet was marked
    //  (so empty cell areas can be copied)
    if ( nCol2>=m_pDoc->MaxCol() && nRow2>=m_pDoc->MaxRow() )
    {
        SCROW nMaxRow;
        SCCOL nMaxCol;
        GetAreaSize( *m_pDoc, nTab1, nTab2, nMaxRow, nMaxCol );
        if( nMaxRow < nRow2 )
            nRow2 = nMaxRow;
        if( nMaxCol < nCol2 )
            nCol2 = nMaxCol;
    }

    m_aBlock = ScRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
    m_nVisibleTab = nTab1;    // valid table as default

    tools::Rectangle aMMRect = m_pDoc->GetMMRect( nCol1,nRow1, nCol2,nRow2, nTab1 );
    m_aObjDesc.maSize = aMMRect.GetSize();
    PrepareOLE( m_aObjDesc );
}

ScTransferObj::~ScTransferObj()
{
    SolarMutexGuard aSolarGuard;

    ScModule* pScMod = ScModule::get();
    const ScDragData* pDragData = pScMod ? pScMod->GetDragData() : nullptr;
    if (pDragData && pDragData->pCellTransfer == this)
    {
        OSL_FAIL("ScTransferObj wasn't released");
        pScMod->ResetDragObject();
    }

    m_pDoc.reset();        // ScTransferObj is owner of clipboard document

    m_aDocShellRef.clear();   // before releasing the mutex

    m_aDrawPersistRef.clear();                    // after the model

}

ScTransferObj* ScTransferObj::GetOwnClipboard(const uno::Reference<datatransfer::XTransferable2>& xTransferable)
{
    return dynamic_cast<ScTransferObj*>(xTransferable.get());
}

void ScTransferObj::AddSupportedFormats()
{
    //  same formats as in ScSelectionTransferObj::AddSupportedFormats
    AddFormat( SotClipboardFormatId::EMBED_SOURCE );
    AddFormat( SotClipboardFormatId::OBJECTDESCRIPTOR );

    // Skip images for cell selections under COKit, they are rendered
    // incorrectly as `ScViewData::GetPosX/GetPosY` returns 0 under COKit.
    if (!comphelper::COKit::isActive())
    {
        AddFormat(SotClipboardFormatId::GDIMETAFILE);
        AddFormat(SotClipboardFormatId::PNG);
        AddFormat(SotClipboardFormatId::BITMAP);
    }

    // ScImportExport formats
    AddFormat( SotClipboardFormatId::HTML );
    AddFormat( SotClipboardFormatId::SYLK );
    AddFormat( SotClipboardFormatId::LINK );
    AddFormat( SotClipboardFormatId::DIF );
    AddFormat( SotClipboardFormatId::STRING );
    AddFormat( SotClipboardFormatId::STRING_TSVC );

    AddFormat( SotClipboardFormatId::RTF );
    AddFormat( SotClipboardFormatId::RICHTEXT );
    AddFormat( SotClipboardFormatId::MARKDOWN );
    AddFormat( SotClipboardFormatId::MARKDOWN_ANNOTATED );
    if ( m_aBlock.aStart == m_aBlock.aEnd )
    {
        AddFormat( SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT );
    }
}

static ScRange lcl_reduceBlock(const ScDocument& rDoc, ScRange aReducedBlock, bool bIncludeVisual = false)
{
    if ((aReducedBlock.aEnd.Col() == rDoc.MaxCol() || aReducedBlock.aEnd.Row() == rDoc.MaxRow()) &&
        aReducedBlock.aStart.Tab() == aReducedBlock.aEnd.Tab())
    {
        // Shrink the block here so we don't waste time creating huge
        // output when whole columns or rows are selected.

        SCCOL nPrintAreaEndCol = 0;
        SCROW nPrintAreaEndRow = 0;
        if (bIncludeVisual)
            rDoc.GetPrintArea( aReducedBlock.aStart.Tab(), nPrintAreaEndCol, nPrintAreaEndRow, true );

        // Shrink the area to allow pasting to external applications.
        // Shrink to real data area for HTML, RTF and RICHTEXT, but include
        // all objects and top-left area for BITMAP and PNG.
        SCCOL nStartCol = aReducedBlock.aStart.Col();
        SCROW nStartRow = aReducedBlock.aStart.Row();
        SCCOL nEndCol = aReducedBlock.aEnd.Col();
        SCROW nEndRow = aReducedBlock.aEnd.Row();

        if (bIncludeVisual)
        {
            ScDataAreaExtras aDataAreaExtras;
            aDataAreaExtras.mbCellNotes = true;
            aDataAreaExtras.mbCellDrawObjects = true;
            bool bShrunk = false;
            rDoc.ShrinkToUsedDataArea( bShrunk, aReducedBlock.aStart.Tab(), nStartCol, nStartRow, nEndCol, nEndRow,
                    false, true /*bStickyTopRow*/, true /*bStickyLeftCol*/, &aDataAreaExtras);
            aDataAreaExtras.GetOverallRange( nStartCol, nStartRow, nEndCol, nEndRow, ScDataAreaExtras::Clip::None);
        }
        else
        {
            bool bShrunk = false;
            rDoc.ShrinkToUsedDataArea( bShrunk, aReducedBlock.aStart.Tab(), nStartCol, nStartRow, nEndCol, nEndRow,
                    false, false /*bStickyTopRow*/, false /*bStickyLeftCol*/);
        }

        if ( nPrintAreaEndRow > nEndRow )
            nEndRow = nPrintAreaEndRow;

        if ( nPrintAreaEndCol > nEndCol )
            nEndCol = nPrintAreaEndCol;

        aReducedBlock = ScRange(nStartCol, nStartRow, aReducedBlock.aStart.Tab(), nEndCol, nEndRow, aReducedBlock.aEnd.Tab());
    }
    return aReducedBlock;
}

OUString ScTransferObj::GetMarkdownFromRange(ScDocument& rDoc, const ScRange& rRange,
                                             bool bAnnotated)
{
    const SCTAB nTab = rRange.aStart.Tab();
    const SCCOL nStartCol = rRange.aStart.Col();
    const SCROW nStartRow = rRange.aStart.Row();
    const SCCOL nEndCol = rRange.aEnd.Col();
    const SCROW nEndRow = rRange.aEnd.Row();

    // When the source is a multi-range clipboard (ctrl-clicked
    // non-adjacent columns or rows), the bounding rectangle includes gap
    // columns/rows that the user never selected. Filter them out so the
    // markdown table only contains the originally selected ranges
    // instead of confusing readers with rows of empty cells.
    const ScRangeList* pRanges = nullptr;
    if (rDoc.IsClipboard())
    {
        const ScRangeList& rClipRanges = rDoc.GetClipParam().maRanges;
        if (rClipRanges.size() > 1)
            pRanges = &rClipRanges;
    }

    auto fnColInRanges = [pRanges](SCCOL nCol) -> bool
    {
        if (!pRanges)
            return true;
        for (size_t i = 0; i < pRanges->size(); ++i)
        {
            const ScRange& rR = (*pRanges)[i];
            if (rR.aStart.Col() <= nCol && nCol <= rR.aEnd.Col())
                return true;
        }
        return false;
    };
    auto fnRowInRanges = [pRanges](SCROW nRow) -> bool
    {
        if (!pRanges)
            return true;
        for (size_t i = 0; i < pRanges->size(); ++i)
        {
            const ScRange& rR = (*pRanges)[i];
            if (rR.aStart.Row() <= nRow && nRow <= rR.aEnd.Row())
                return true;
        }
        return false;
    };

    // Escape pipe and newline in cell values
    auto fnEscapeCell = [](const OUString& rStr) -> OString
    {
        OUString aEscaped = rStr.replaceAll(u"\\", u"\\\\")
                                .replaceAll(u"|", u"\\|")
                                .replaceAll(u"\n", u" ")
                                .replaceAll(u"\r", u"");
        return OUStringToOString(aEscaped, RTL_TEXTENCODING_UTF8);
    };

    OStringBuffer aBuf;

    // Header row
    if (bAnnotated)
    {
        aBuf.append("| Row |");
        for (SCCOL nCol = nStartCol; nCol <= nEndCol; nCol++)
        {
            if (!fnColInRanges(nCol))
                continue;
            aBuf.append(OString::Concat(" ")
                        + OUStringToOString(ScColToAlpha(nCol), RTL_TEXTENCODING_UTF8) + " |");
        }
    }
    else
    {
        aBuf.append("|");
        for (SCCOL nCol = nStartCol; nCol <= nEndCol; nCol++)
        {
            if (!fnColInRanges(nCol))
                continue;
            OUString aCellStr = rDoc.GetString(nCol, nStartRow, nTab);
            aBuf.append(" " + fnEscapeCell(aCellStr) + " |");
        }
    }
    aBuf.append("\n");

    // Separator
    if (bAnnotated)
        aBuf.append("| --- |");
    else
        aBuf.append("|");
    for (SCCOL nCol = nStartCol; nCol <= nEndCol; nCol++)
    {
        if (!fnColInRanges(nCol))
            continue;
        aBuf.append(" --- |");
    }
    aBuf.append("\n");

    // Data rows
    SCROW nDataStart = bAnnotated ? nStartRow : nStartRow + 1;
    for (SCROW nRow = nDataStart; nRow <= nEndRow; nRow++)
    {
        if (!fnRowInRanges(nRow))
            continue;
        if (bAnnotated)
            aBuf.append(OString::Concat("| ") + OString::number(static_cast<int>(nRow + 1)) + " |");
        else
            aBuf.append("|");
        for (SCCOL nCol = nStartCol; nCol <= nEndCol; nCol++)
        {
            if (!fnColInRanges(nCol))
                continue;
            OUString aCellStr = rDoc.GetString(nCol, nRow, nTab);
            aBuf.append(" " + fnEscapeCell(aCellStr) + " |");
        }
        aBuf.append("\n");
    }

    return OStringToOUString(aBuf.makeStringAndClear(), RTL_TEXTENCODING_UTF8);
}

bool ScTransferObj::GetData( const datatransfer::DataFlavor& rFlavor, const OUString& /*rDestDoc*/ )
{
    SotClipboardFormatId nFormat = SotExchange::GetFormat( rFlavor );
    bool        bOK = false;

    if( HasFormat( nFormat ) )
    {
        ScRange aReducedBlock = m_aBlock;

        bool bReduceBlockFormat =
            nFormat == SotClipboardFormatId::HTML
            || nFormat == SotClipboardFormatId::RTF
            || nFormat == SotClipboardFormatId::RICHTEXT
            || nFormat == SotClipboardFormatId::MARKDOWN
            || nFormat == SotClipboardFormatId::MARKDOWN_ANNOTATED
            || nFormat == SotClipboardFormatId::BITMAP
            || nFormat == SotClipboardFormatId::PNG;

        const bool bIncludeVisual = (nFormat == SotClipboardFormatId::BITMAP ||
                                     nFormat == SotClipboardFormatId::PNG);

        if (bReduceBlockFormat)
            aReducedBlock = lcl_reduceBlock(*m_pDoc, m_aBlock, bIncludeVisual);

        if ( nFormat == SotClipboardFormatId::LINKSRCDESCRIPTOR || nFormat == SotClipboardFormatId::OBJECTDESCRIPTOR )
        {
            bOK = SetTransferableObjectDescriptor( m_aObjDesc );
        }
        else if ( ( nFormat == SotClipboardFormatId::MARKDOWN
                    || nFormat == SotClipboardFormatId::MARKDOWN_ANNOTATED )
                  && m_aBlock.aStart == m_aBlock.aEnd )
        {
            // Markdown from a single cell - use EditEngine
            SCCOL nCol = m_aBlock.aStart.Col();
            SCROW nRow = m_aBlock.aStart.Row();
            SCTAB nTab = m_aBlock.aStart.Tab();
            ScAddress aPos(nCol, nRow, nTab);

            const ScPatternAttr* pPattern = m_pDoc->GetPattern(nCol, nRow, nTab);
            if (pPattern)
            {
                ScTabEditEngine aEngine(*pPattern, m_pDoc->GetEditEnginePool(), *m_pDoc);
                ScRefCellValue aCell(*m_pDoc, aPos);
                if (aCell.getType() == CELLTYPE_EDIT)
                {
                    const EditTextObject* pObj = aCell.getEditText();
                    aEngine.SetTextCurrentDefaults(*pObj);
                }
                else
                {
                    ScInterpreterContext& rContext = m_pDoc->GetNonThreadedContext();
                    sal_uInt32 nNumFmt = pPattern->GetNumberFormat(rContext);
                    const Color* pColor;
                    OUString aText
                        = ScCellFormat::GetString(aCell, nNumFmt, &pColor, &rContext, *m_pDoc);
                    if (!aText.isEmpty())
                        aEngine.SetTextCurrentDefaults(aText);
                }

                sal_Int32 nParCnt = aEngine.GetParagraphCount();
                if (nParCnt == 0)
                    nParCnt = 1;
                ESelection aSel(0, 0, nParCnt - 1, aEngine.GetTextLen(nParCnt - 1));

                uno::Reference<datatransfer::XTransferable> xEditTrans
                    = aEngine.CreateTransferable(aSel);
                TransferableDataHelper aEditHelper(xEditTrans);

                OUString aMarkdown;
                bOK = aEditHelper.GetString(SotClipboardFormatId::MARKDOWN, aMarkdown);
                if (bOK)
                {
                    bOK = SetAny(cpo::uno::Any(aMarkdown));
                }
            }
        }
        else if ( ( nFormat == SotClipboardFormatId::MARKDOWN
                    || nFormat == SotClipboardFormatId::MARKDOWN_ANNOTATED )
                  && m_aBlock.aStart != m_aBlock.aEnd )
        {
            // Markdown table from multi-cell range
            ScRange aRange = lcl_reduceBlock(*m_pDoc, m_aBlock);
            bool bAnnotated = (nFormat == SotClipboardFormatId::MARKDOWN_ANNOTATED);
            bOK = SetAny(cpo::uno::Any(
                ScTransferObj::GetMarkdownFromRange(*m_pDoc, aRange, bAnnotated)));
        }
        else if ( ( nFormat == SotClipboardFormatId::RTF || nFormat == SotClipboardFormatId::RICHTEXT ||
            nFormat == SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT ) && m_aBlock.aStart == m_aBlock.aEnd )
        {
            //  RTF from a single cell is handled by EditEngine

            SCCOL nCol = m_aBlock.aStart.Col();
            SCROW nRow = m_aBlock.aStart.Row();
            SCTAB nTab = m_aBlock.aStart.Tab();
            ScAddress aPos(nCol, nRow, nTab);

            const ScPatternAttr* pPattern = m_pDoc->GetPattern( nCol, nRow, nTab );
            if (pPattern)
            {
                ScTabEditEngine aEngine(*pPattern, m_pDoc->GetEditEnginePool(), *m_pDoc);
                ScRefCellValue aCell(*m_pDoc, aPos);
                if (aCell.getType() == CELLTYPE_EDIT)
                {
                    const EditTextObject* pObj = aCell.getEditText();
                    aEngine.SetTextCurrentDefaults(*pObj);
                }
                else
                {
                    ScInterpreterContext& rContext = m_pDoc->GetNonThreadedContext();
                    sal_uInt32 nNumFmt = pPattern->GetNumberFormat(rContext);
                    const Color* pColor;
                    OUString aText
                        = ScCellFormat::GetString(aCell, nNumFmt, &pColor, &rContext, *m_pDoc);
                    if (!aText.isEmpty())
                        aEngine.SetTextCurrentDefaults(aText);
                }

                bOK = SetObject(&aEngine,
                                ((nFormat == SotClipboardFormatId::RTF)
                                     ? SCTRANS_TYPE_EDIT_RTF
                                     : ((nFormat == SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT)
                                            ? SCTRANS_TYPE_EDIT_ODF_TEXT_FLAT
                                            : SCTRANS_TYPE_EDIT_BIN)),
                                rFlavor);
            }
        }
        else if ( ScImportExport::IsFormatSupported( nFormat ) || nFormat == SotClipboardFormatId::RTF
            || nFormat == SotClipboardFormatId::RICHTEXT )
        {
            //  if this transfer object was used to create a DDE link, filtered rows
            //  have to be included for subsequent calls (to be consistent with link data)
            if ( nFormat == SotClipboardFormatId::LINK )
                m_bUsedForLink = true;

            bool bIncludeFiltered = m_pDoc->IsCutMode() || m_bUsedForLink;

            ScImportExport aObj( *m_pDoc, aReducedBlock );
            // Plain text ("Unformatted text") may contain embedded tabs and
            // line breaks but is not enclosed in quotes. Which makes it
            // unsuitable for multiple cells, especially if one of them is
            // multi-line, but otherwise is expected behavior for plain text.
            // For multiple cells replace embedded line breaks (and tabs) with
            // space character, otherwise pasting would yield odd results.
            /* XXX: it's debatable whether this is actually expected, but
             * there's no way to satisfy all possible requirements when
             * copy/pasting unformatted text. */
            const bool bPlainMulti = (nFormat == SotClipboardFormatId::STRING &&
                    aReducedBlock.aStart != aReducedBlock.aEnd);
            // Add quotes only for STRING_TSVC.
            /* TODO: a possible future STRING_TSV should not contain embedded
             * line breaks nor tab (separator) characters and not be quoted.
             * A possible STRING_CSV should. */
            ScExportTextOptions aTextOptions( ScExportTextOptions::None, 0,
                    (nFormat == SotClipboardFormatId::STRING_TSVC));
            if ( bPlainMulti || m_bUsedForLink )
            {
                // For a DDE link or plain text multiple cells, convert line
                // breaks and separators to space.
                aTextOptions.meNewlineConversion = ScExportTextOptions::ToSpace;
                aTextOptions.mcSeparatorConvertTo = ' ';
                aTextOptions.mbAddQuotes = false;
            }
            aObj.SetExportTextOptions(aTextOptions);
            aObj.SetFormulas(m_pDoc->GetViewOptions().GetOption(sc::ViewOption::FORMULAS));
            aObj.SetIncludeFiltered( bIncludeFiltered );

            //  DataType depends on format type:

            if ( rFlavor.DataType.equals( ::cppu::UnoType<OUString>::get() ) )
            {
                OUString aString;
                if ( aObj.ExportString( aString, nFormat ) )
                    bOK = SetString( aString );
            }
            else if ( rFlavor.DataType.equals( cppu::UnoType<cpo::uno::Sequence< sal_Int8 >>::get() ) )
            {
                //  SetObject converts a stream into an Int8-Sequence
                bOK = SetObject( &aObj, SCTRANS_TYPE_IMPEX, rFlavor );
            }
            else
            {
                OSL_FAIL("unknown DataType");
            }
        }
        else if ( nFormat == SotClipboardFormatId::BITMAP || nFormat == SotClipboardFormatId::PNG )
        {
            tools::Rectangle aMMRect = m_pDoc->GetMMRect( aReducedBlock.aStart.Col(), aReducedBlock.aStart.Row(),
                                                 aReducedBlock.aEnd.Col(), aReducedBlock.aEnd.Row(),
                                                 aReducedBlock.aStart.Tab() );
            ScopedVclPtrInstance< VirtualDevice > pVirtDev;

            // tdf#160855 fix crash due to Skia's internal maximum pixel limit
            // Somewhere in the tens of thousands of selected fill cells,
            // the size of the VirtualDevice exceeds 1 GB of pixels. But
            // Skia, at least on macOS, will fail to create a surface.
            // Even if there is ample free memory, Skia/Raster will fail.
            // The second problem is that even if you disable Skia, the
            // crash is just delayed when a Bitmap is created from the
            // VirtualDevice and malloc() fails.
            // Since this data flavor really triggers one or more system
            // memory limits, lower the resolution of the bitmap by keeping
            // the VirtualDevice pixel size within an arbitrary number of
            // pixels.
            // Note: the arbitrary "maximum number of pixels" limit that
            // that Skia can handle may need to be raised or lowered for
            // platforms other than macOS.
            static constexpr tools::Long nCopyToImageMaxPixels = 8192 * 8192;
            double fScale(1.0);
            Size aPixelSize = pVirtDev->LogicToPixel(aMMRect.GetSize(), MapMode(MapUnit::Map100thMM));
            tools::Long nPixels(aPixelSize.Width() * aPixelSize.Height());
            if (nPixels < 0 || nPixels > nCopyToImageMaxPixels)
            {
                fScale = double(nCopyToImageMaxPixels) / nPixels;
                aPixelSize = pVirtDev->LogicToPixel(aMMRect.GetSize(), MapMode(MapUnit::Map100thMM, Point(), fScale, fScale));
                nPixels = aPixelSize.Width() * aPixelSize.Height();
            }

            pVirtDev->SetOutputSizePixel(aPixelSize);

            PaintToDev( pVirtDev, *m_pDoc, 1.0, aReducedBlock );

            pVirtDev->SetMapMode( MapMode( MapUnit::MapPixel, Point(), fScale, fScale ) );
            Bitmap aBmp( pVirtDev->GetBitmap( Point(), pVirtDev->GetOutputSize() ) );
            bOK = SetBitmap(aBmp, rFlavor);
        }
        else if ( nFormat == SotClipboardFormatId::GDIMETAFILE )
        {
            // #i123405# Do not limit visual size calculation for metafile creation.
            // It seems unlikely that removing the limitation causes problems since
            // metafile creation means that no real pixel device in the needed size is
            // created.
            InitDocShell(false);

            SfxObjectShell* pEmbObj = m_aDocShellRef.get();

            // like SvEmbeddedTransfer::GetData:
            GDIMetaFile     aMtf;
            ScopedVclPtrInstance< VirtualDevice > pVDev;
            MapMode         aMapMode( pEmbObj->GetMapUnit() );
            tools::Rectangle       aVisArea( pEmbObj->GetVisArea( ASPECT_CONTENT ) );

            pVDev->EnableOutput( false );
            pVDev->SetMapMode( aMapMode );
            aMtf.SetPrefSize( aVisArea.GetSize() );
            aMtf.SetPrefMapMode( aMapMode );
            aMtf.Record( pVDev );

            pEmbObj->DoDraw( pVDev, Point(), aVisArea.GetSize(), JobSetup() );

            aMtf.Stop();
            aMtf.WindStart();

            bOK = SetGDIMetaFile( aMtf );
        }
        else if ( nFormat == SotClipboardFormatId::EMBED_SOURCE )
        {
            //TODO/LATER: differentiate between formats?!
            // #i123405# Do limit visual size calculation to PageSize
            InitDocShell(true);         // set aDocShellRef

            SfxObjectShell* pEmbObj = m_aDocShellRef.get();
            bOK = SetObject( pEmbObj, SCTRANS_TYPE_EMBOBJ, rFlavor );
        }
    }
    return bOK;
}

bool ScTransferObj::WriteObject( SvStream& rOStm, void* pUserObject, sal_uInt32 nUserObjectId,
                                        const datatransfer::DataFlavor& rFlavor )
{
    // called from SetObject, put data into stream

    bool bRet = false;
    switch (nUserObjectId)
    {
        case SCTRANS_TYPE_IMPEX:
            {
                ScImportExport* pImpEx = static_cast<ScImportExport*>(pUserObject);

                SotClipboardFormatId nFormat = SotExchange::GetFormat( rFlavor );
                // mba: no BaseURL for data exchange
                if ( pImpEx->ExportStream( rOStm, OUString(), nFormat ) )
                    bRet = ( rOStm.GetError() == ERRCODE_NONE );
            }
            break;

        case SCTRANS_TYPE_EDIT_RTF:
        case SCTRANS_TYPE_EDIT_BIN:
            {
                ScTabEditEngine* pEngine = static_cast<ScTabEditEngine*>(pUserObject);
                if ( nUserObjectId == SCTRANS_TYPE_EDIT_RTF )
                {
                    pEngine->Write( rOStm, EETextFormat::Rtf );
                    bRet = ( rOStm.GetError() == ERRCODE_NONE );
                }
                else
                {
                    //  can't use Write for EditEngine format because that would
                    //  write old format without support for unicode characters.
                    //  Get the data from the EditEngine's transferable instead.

                    sal_Int32 nParCnt = pEngine->GetParagraphCount();
                    if ( nParCnt == 0 )
                        nParCnt = 1;
                    ESelection aSel( 0, 0, nParCnt-1, pEngine->GetTextLen(nParCnt-1) );

                    uno::Reference<datatransfer::XTransferable> xEditTrans = pEngine->CreateTransferable( aSel );
                    TransferableDataHelper aEditHelper( xEditTrans );

                    std::unique_ptr<SvStream> xStrm = aEditHelper.GetSotStorageStream( rFlavor );
                    bRet = bool(xStrm);
                    if (bRet)
                        rOStm.WriteStream(*xStrm);
                }
            }
            break;

        case SCTRANS_TYPE_EDIT_ODF_TEXT_FLAT:
            {
                ScTabEditEngine* pEngine = static_cast<ScTabEditEngine*>(pUserObject);
                pEngine->Write(rOStm, EETextFormat::Xml);
                bRet = (rOStm.GetError() == ERRCODE_NONE);
            }
            break;

        case SCTRANS_TYPE_EMBOBJ:
            {
                // TODO/MBA: testing
                SfxObjectShell*   pEmbObj = static_cast<SfxObjectShell*>(pUserObject);
                ::utl::TempFileFast aTempFile;
                SvStream* pTempStream = aTempFile.GetStream(StreamMode::READWRITE);
                uno::Reference< embed::XStorage > xWorkStore =
                    ::comphelper::OStorageHelper::GetStorageFromStream( new utl::OStreamWrapper(*pTempStream) );

                // write document storage
                pEmbObj->SetupStorage( xWorkStore, SOFFICE_FILEFORMAT_CURRENT, false );

                // mba: no relative URLs for clipboard!
                SfxMedium aMedium( xWorkStore, OUString() );
                pEmbObj->DoSaveObjectAs( aMedium, false );
                pEmbObj->DoSaveCompleted();

                uno::Reference< embed::XTransactedObject > xTransact( xWorkStore, uno::UNO_QUERY );
                if ( xTransact.is() )
                    xTransact->commit();

                rOStm.SetBufferSize( 0xff00 );
                rOStm.WriteStream( *pTempStream );

                bRet = true;

                xWorkStore->dispose();
                xWorkStore.clear();
            }
            break;

        default:
            OSL_FAIL("unknown object id");
    }
    return bRet;
}

bool SAL_CALL ScTransferObj::isComplex()
{
    ScRange aReduced = lcl_reduceBlock(*m_pDoc, m_aBlock);
    size_t nCells = (aReduced.aEnd.Col() - aReduced.aStart.Col() + 1) *
                    (aReduced.aEnd.Row() - aReduced.aStart.Row() + 1) *
                    (aReduced.aEnd.Tab() - aReduced.aStart.Tab() + 1);
    return nCells > 1000;
}

void ScTransferObj::DragFinished( sal_Int8 nDropAction )
{
    if ( nDropAction == DND_ACTION_MOVE && !m_bDragWasInternal && !(m_nDragSourceFlags & ScDragSrc::Navigator) )
    {
        //  move: delete source data
        ScDocShell* pSourceSh = GetSourceDocShell();
        if (pSourceSh)
        {
            ScMarkData aMarkData = GetSourceMarkData();
            //  external drag&drop doesn't copy objects, so they also aren't deleted:
            //  bApi=TRUE, don't show error messages from drag&drop
            pSourceSh->GetDocFunc().DeleteContents( aMarkData, InsertDeleteFlags::ALL & ~InsertDeleteFlags::OBJECTS, true, true );
        }
    }

    ScModule* pScMod = ScModule::get();
    const ScDragData* pDragData = pScMod ? pScMod->GetDragData() : nullptr;
    if (pDragData && pDragData->pCellTransfer == this)
        pScMod->ResetDragObject();

    m_xDragSourceRanges = nullptr;       // don't keep source after dropping

    TransferDataContainer::DragFinished( nDropAction );
}

void ScTransferObj::SetDragHandlePos( SCCOL nX, SCROW nY )
{
    m_nDragHandleX = nX;
    m_nDragHandleY = nY;
}

void ScTransferObj::SetSourceCursorPos( SCCOL nX, SCROW nY )
{
    m_nSourceCursorX = nX;
    m_nSourceCursorY = nY;
}

bool ScTransferObj::WasSourceCursorInSelection() const
{
    return
        m_nSourceCursorX >= m_aBlock.aStart.Col() && m_nSourceCursorX <= m_aBlock.aEnd.Col() &&
        m_nSourceCursorY >= m_aBlock.aStart.Row() && m_nSourceCursorY <= m_aBlock.aEnd.Row();
}

void ScTransferObj::SetVisibleTab( SCTAB nNew )
{
    m_nVisibleTab = nNew;
}

void ScTransferObj::SetDrawPersist( const SfxObjectShellRef& rRef )
{
    m_aDrawPersistRef = rRef;
}

void ScTransferObj::SetDragSource( ScDocShell* pSourceShell, const ScMarkData& rMark )
{
    ScRangeList aRanges;
    rMark.FillRangeListWithMarks( &aRanges, false );
    m_xDragSourceRanges = new ScCellRangesObj( pSourceShell, aRanges );
}

void ScTransferObj::SetDragSourceFlags(ScDragSrc nFlags)
{
    m_nDragSourceFlags = nFlags;
}

void ScTransferObj::SetDragWasInternal()
{
    m_bDragWasInternal = true;
}

void ScTransferObj::SetUseInApi( bool bSet )
{
    m_bUseInApi = bSet;
}

ScDocument* ScTransferObj::GetSourceDocument()
{
    ScDocShell* pSourceDocSh = GetSourceDocShell();
    if (pSourceDocSh)
        return &pSourceDocSh->GetDocument();
    return nullptr;
}

ScDocShell* ScTransferObj::GetSourceDocShell()
{
    if (m_xDragSourceRanges)
        return m_xDragSourceRanges->GetDocShell();

    return nullptr;    // none set
}

ScMarkData ScTransferObj::GetSourceMarkData() const
{
    ScMarkData aMarkData(m_pDoc->GetSheetLimits());
    if (m_xDragSourceRanges)
    {
        const ScRangeList& rRanges = m_xDragSourceRanges->GetRangeList();
        aMarkData.MarkFromRangeList( rRanges, false );
    }
    return aMarkData;
}

//  initialize aDocShellRef with a live document from the ClipDoc

// #i123405# added parameter to allow size calculation without limitation
// to PageSize, e.g. used for Metafile creation for clipboard.

void ScTransferObj::InitDocShell(bool bLimitToPageSize)
{
    if ( m_aDocShellRef.is() )
        return;

    m_aDocShellRef = new ScDocShell; // ref must be there before InitNew

    m_aDocShellRef->DoInitNew();

    ScDocument& rDestDoc = m_aDocShellRef->GetDocument();
    ScMarkData aDestMark(rDestDoc.GetSheetLimits());
    aDestMark.SelectTable( 0, true );

    rDestDoc.SetDocOptions( m_pDoc->GetDocOptions() );   // #i42666#

    OUString aTabName;
    m_pDoc->GetName( m_aBlock.aStart.Tab(), aTabName );
    rDestDoc.RenameTab( 0, aTabName );

    m_aDocShellRef->MakeDrawLayer();

    rDestDoc.CopyStdStylesFrom(*m_pDoc);

    SCCOL nStartX = m_aBlock.aStart.Col();
    SCROW nStartY = m_aBlock.aStart.Row();
    SCCOL nEndX = m_aBlock.aEnd.Col();
    SCROW nEndY = m_aBlock.aEnd.Row();

    //  widths / heights
    //  (must be copied before CopyFromClip, for drawing objects)

    SCCOL nCol;
    SCTAB nSrcTab = m_aBlock.aStart.Tab();
    rDestDoc.SetLayoutRTL(0, m_pDoc->IsLayoutRTL(nSrcTab));
    for (nCol=nStartX; nCol<=nEndX; nCol++)
        if ( m_pDoc->ColHidden(nCol, nSrcTab) )
            rDestDoc.ShowCol( nCol, 0, false );
        else
            rDestDoc.SetColWidth( nCol, 0, m_pDoc->GetColWidth( nCol, nSrcTab ) );

    if (nStartY > 0)
    {
        // Set manual height for all previous rows so we can ensure
        // that visible area will not change due to autoheight
        rDestDoc.SetManualHeight(0, nStartY - 1, 0, true);
    }
    for (SCROW nRow = nStartY; nRow <= nEndY; ++nRow)
    {
        if ( m_pDoc->RowHidden(nRow, nSrcTab) )
            rDestDoc.ShowRow( nRow, 0, false );
        else
        {
            rDestDoc.SetRowHeight( nRow, 0, m_pDoc->GetOriginalHeight( nRow, nSrcTab ) );

            //  if height was set manually, that flag has to be copied, too
            bool bManual = m_pDoc->IsManualRowHeight(nRow, nSrcTab);
            rDestDoc.SetManualHeight(nRow, nRow, 0, bManual);
        }
    }

    //  cell range is copied to the original position, but on the first sheet
    //  -> bCutMode must be set
    //  pDoc is always a Clipboard-document

    ScRange aDestRange( nStartX,nStartY,0, nEndX,nEndY,0 );
    bool bWasCut = m_pDoc->IsCutMode();
    if (!bWasCut)
        m_pDoc->SetClipArea( aDestRange, true );          // Cut
    rDestDoc.CopyFromClip( aDestRange, aDestMark, InsertDeleteFlags::ALL, nullptr, m_pDoc.get(), false );
    m_pDoc->SetClipArea( aDestRange, bWasCut );

    StripRefs(*m_pDoc, nStartX,nStartY, nEndX,nEndY, rDestDoc);

    ScRange aMergeRange = aDestRange;
    rDestDoc.ExtendMerge( aMergeRange, true );

    m_pDoc->CopyDdeLinks( rDestDoc );         // copy values of DDE Links

    //  page format (grid etc) and page size (maximum size for ole object)

    Size aPaperSize = SvxPaperInfo::GetPaperSize( PAPER_A4 );       // Twips
    ScStyleSheetPool* pStylePool = m_pDoc->GetStyleSheetPool();
    OUString aStyleName = m_pDoc->GetPageStyle( m_aBlock.aStart.Tab() );
    SfxStyleSheetBase* pStyleSheet = pStylePool->Find( aStyleName, SfxStyleFamily::Page );
    if (pStyleSheet)
    {
        const SfxItemSet& rSourceSet = pStyleSheet->GetItemSet();
        aPaperSize = rSourceSet.Get(ATTR_PAGE_SIZE).GetSize();

        // CopyStyleFrom copies SetItems with correct pool
        ScStyleSheetPool* pDestPool = rDestDoc.GetStyleSheetPool();
        pDestPool->CopyStyleFrom( pStylePool, aStyleName, SfxStyleFamily::Page );
    }

    ScViewData aViewData(*m_aDocShellRef, nullptr);
    aViewData.SetScreen( nStartX,nStartY, nEndX,nEndY );
    aViewData.SetCurX( nStartX );
    aViewData.SetCurY( nStartY );

    rDestDoc.SetViewOptions( m_pDoc->GetViewOptions() );

    //      Size
    //! get while copying sizes

    tools::Long nPosX = 0;
    tools::Long nPosY = 0;

    for (nCol=0; nCol<nStartX; nCol++)
        nPosX += rDestDoc.GetColWidth( nCol, 0 );
    nPosY += rDestDoc.GetRowHeight( 0, nStartY-1, 0 );
    nPosX = o3tl::convert(nPosX, o3tl::Length::twip, o3tl::Length::mm100);
    nPosY = o3tl::convert(nPosY, o3tl::Length::twip, o3tl::Length::mm100);

    aPaperSize.setWidth( aPaperSize.Width() * 2 );       // limit OLE object to double of page size
    aPaperSize.setHeight( aPaperSize.Height() * 2 );

    tools::Long nSizeX = 0;
    tools::Long nSizeY = 0;
    for (nCol=nStartX; nCol<=nEndX; nCol++)
    {
        tools::Long nAdd = rDestDoc.GetColWidth( nCol, 0 );
        if ( bLimitToPageSize && nSizeX+nAdd > aPaperSize.Width() && nSizeX )   // above limit?
            break;
        nSizeX += nAdd;
    }
    for (SCROW nRow=nStartY; nRow<=nEndY; nRow++)
    {
        tools::Long nAdd = rDestDoc.GetRowHeight( nRow, 0 );
        if ( bLimitToPageSize && nSizeY+nAdd > aPaperSize.Height() && nSizeY )  // above limit?
            break;
        nSizeY += nAdd;
    }
    nSizeX = o3tl::convert(nSizeX, o3tl::Length::twip, o3tl::Length::mm100);
    nSizeY = o3tl::convert(nSizeY, o3tl::Length::twip, o3tl::Length::mm100);

//      m_aDocShellRef->SetVisAreaSize( Size(nSizeX,nSizeY) );

    tools::Rectangle aNewArea( Point(nPosX,nPosY), Size(nSizeX,nSizeY) );
    //TODO/LATER: why twice?!
    //m_aDocShellRef->SvInPlaceObject::SetVisArea( aNewArea );
    m_aDocShellRef->SetVisArea(aNewArea);

    m_aDocShellRef->UpdateOle(aViewData, true);

    //! SetDocumentModified?
    if ( rDestDoc.IsChartListenerCollectionNeedsUpdate() )
        rDestDoc.UpdateChartListenerCollection();
}

SfxObjectShell* ScTransferObj::SetDrawClipDoc( bool bAnyOle, const std::shared_ptr<ScDocument>& pDoc )
{
    // update ScGlobal::xDrawClipDocShellRef

    ScGlobal::xDrawClipDocShellRef.clear();
    if (bAnyOle)
    {
        ScGlobal::xDrawClipDocShellRef = new ScDocShell(SfxModelFlags::EMBEDDED_OBJECT | SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS, pDoc); // there must be a ref
        ScGlobal::xDrawClipDocShellRef->DoInitNew();
    }

    return ScGlobal::xDrawClipDocShellRef.get();
}

void ScTransferObj::StripRefs( ScDocument& rDoc,
                    SCCOL nStartX, SCROW nStartY, SCCOL nEndX, SCROW nEndY,
                    ScDocument& rDestDoc )
{
    //  In a clipboard doc the data don't have to be on the first sheet

    SCTAB nSrcTab = 0;
    while (nSrcTab < rDoc.GetTableCount() && !rDoc.HasTable(nSrcTab))
        ++nSrcTab;
    SCTAB nDestTab = 0;
    while (nDestTab < rDestDoc.GetTableCount() && !rDestDoc.HasTable(nDestTab))
        ++nDestTab;

    if (!rDoc.HasTable(nSrcTab) || !rDestDoc.HasTable(nDestTab))
    {
        OSL_FAIL("Sheet not found in ScTransferObj::StripRefs");
        return;
    }

    ScRange aRef;

    ScCellIterator aIter( rDoc, ScRange(nStartX, nStartY, nSrcTab, nEndX, nEndY, nSrcTab) );
    for (bool bHas = aIter.first(); bHas; bHas = aIter.next())
    {
        if (aIter.getType() != CELLTYPE_FORMULA)
            continue;

        ScFormulaCell* pFCell = aIter.getFormulaCell();
        bool bOut = false;
        ScDetectiveRefIter aRefIter( rDoc, pFCell );
        while ( !bOut && aRefIter.GetNextRef( aRef ) )
        {
            if ( aRef.aStart.Tab() != nSrcTab || aRef.aEnd.Tab() != nSrcTab ||
                    aRef.aStart.Col() < nStartX || aRef.aEnd.Col() > nEndX ||
                    aRef.aStart.Row() < nStartY || aRef.aEnd.Row() > nEndY )
                bOut = true;
        }
        if (bOut)
        {
            SCCOL nCol = aIter.GetPos().Col();
            SCROW nRow = aIter.GetPos().Row();

            FormulaError nErrCode = pFCell->GetErrCode();
            ScAddress aPos(nCol, nRow, nDestTab);
            if (nErrCode != FormulaError::NONE)
            {
                if ( rDestDoc.GetAttr( nCol,nRow,nDestTab, ATTR_HOR_JUSTIFY).GetValue() ==
                        SvxCellHorJustify::Standard )
                    rDestDoc.ApplyAttr( nCol,nRow,nDestTab,
                            SvxHorJustifyItem(SvxCellHorJustify::Right, ATTR_HOR_JUSTIFY) );

                ScSetStringParam aParam;
                aParam.setTextInput();
                rDestDoc.SetString(aPos, ScGlobal::GetErrorString(nErrCode), &aParam);
            }
            else if (pFCell->IsValue())
            {
                rDestDoc.SetValue(aPos, pFCell->GetValue());
            }
            else
            {
                OUString aStr = pFCell->GetString().getString();
                if ( pFCell->IsMultilineResult() )
                {
                    ScFieldEditEngine& rEngine = rDestDoc.GetEditEngine();
                    rEngine.SetTextCurrentDefaults(aStr);
                    rDestDoc.SetEditText(ScAddress(nCol,nRow,nDestTab), rEngine.CreateTextObject());
                }
                else
                {
                    ScSetStringParam aParam;
                    aParam.setTextInput();
                    rDestDoc.SetString(aPos, aStr, &aParam);
                }
            }
        }
    }
}

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