/* -*- 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 .
 */

#ifdef _WIN32
#include <prewin.h>
#include <postwin.h>
#include <shlobj.h>
#endif
#include <o3tl/char16_t2wchar_t.hxx>
#include <rtl/uri.hxx>
#include <rtl/tencinfo.h>
#include <sal/log.hxx>
#include <tools/debug.hxx>
#include <tools/urlobj.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <sot/exchange.hxx>
#include <sot/storage.hxx>
#include <vcl/alpha.hxx>
#include <vcl/bitmap.hxx>
#include <vcl/filter/SvmReader.hxx>
#include <vcl/filter/SvmWriter.hxx>
#include <vcl/gdimtf.hxx>
#include <vcl/graph.hxx>
#include <vcl/cvtgrf.hxx>
#include <vcl/svapp.hxx>
#include <vcl/window.hxx>
#include <comphelper/fileformat.h>
#include <comphelper/processfactory.hxx>
#include <comphelper/scopeguard.hxx>
#include <comphelper/servicehelper.hxx>
#include <comphelper/sequence.hxx>
#include <sot/filelist.hxx>
#include <cppuhelper/implbase.hxx>

#include <comphelper/seqstream.hxx>
#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp>
#include <com/sun/star/datatransfer/XMimeContentType.hpp>
#include <com/sun/star/datatransfer/XTransferable2.hpp>
#include <com/sun/star/frame/Desktop.hpp>

#include <svl/urlbmk.hxx>
#include <vcl/inetimg.hxx>
#include <vcl/wmf.hxx>
#include <vcl/imap.hxx>
#include <vcl/transfer.hxx>
#include <rtl/strbuf.hxx>
#include <cstdio>
#include <vcl/dibtools.hxx>
#include <vcl/filter/PngImageReader.hxx>
#include <vcl/filter/PngImageWriter.hxx>
#include <vcl/graphicfilter.hxx>
#include <memory>
#include <utility>
#include <vcl/TypeSerializer.hxx>

using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::frame;
using namespace ::com::sun::star::io;
using namespace ::com::sun::star::datatransfer;
using namespace ::com::sun::star::datatransfer::clipboard;
using namespace ::com::sun::star::datatransfer::dnd;
using namespace std::literals::string_view_literals;


#define TOD_SIG1 0x01234567
#define TOD_SIG2 0x89abcdef

SvStream& WriteTransferableObjectDescriptor( SvStream& rOStm, const TransferableObjectDescriptor& rObjDesc )
{
    const sal_uInt64    nFirstPos = rOStm.Tell();
    const sal_uInt32    nViewAspect = rObjDesc.mnViewAspect;
    const sal_uInt32    nSig1 = TOD_SIG1, nSig2 = TOD_SIG2;

    rOStm.SeekRel( 4 );
    WriteSvGlobalName( rOStm, rObjDesc.maClassName );
    rOStm.WriteUInt32( nViewAspect );
    rOStm.WriteInt32( rObjDesc.maSize.Width() );
    rOStm.WriteInt32( rObjDesc.maSize.Height() );
    rOStm.WriteInt32( rObjDesc.maDragStartPos.X() );
    rOStm.WriteInt32( rObjDesc.maDragStartPos.Y() );
    rOStm.WriteUniOrByteString( rObjDesc.maTypeName, osl_getThreadTextEncoding() );
    rOStm.WriteUniOrByteString( rObjDesc.maDisplayName, osl_getThreadTextEncoding() );
    rOStm.WriteUInt32( nSig1 ).WriteUInt32( nSig2 );

    const sal_uInt64 nLastPos = rOStm.Tell();

    rOStm.Seek( nFirstPos );
    rOStm.WriteUInt32( nLastPos - nFirstPos  );
    rOStm.Seek( nLastPos );

    return rOStm;
}

static void TryReadTransferableObjectDescriptor(SvStream& rIStm,
                                                TransferableObjectDescriptor& rObjDesc)
{
    auto nStartPos = rIStm.Tell();
    comphelper::ScopeGuard streamPosRestore([nStartPos, &rIStm] { rIStm.Seek(nStartPos); });

    sal_uInt32 size;
    rIStm.ReadUInt32(size);

    SvGlobalName className;
    rIStm >> className;

    sal_uInt32 viewAspect;
    rIStm.ReadUInt32(viewAspect);

    sal_Int32 width, height;
    rIStm.ReadInt32(width).ReadInt32(height);

    sal_Int32 dragStartPosX, dragStartPosY;
    rIStm.ReadInt32(dragStartPosX).ReadInt32(dragStartPosY);

    const OUString typeName = rIStm.ReadUniOrByteString(osl_getThreadTextEncoding());
    const OUString displayName = rIStm.ReadUniOrByteString(osl_getThreadTextEncoding());

    sal_uInt32 nSig1, nSig2;
    rIStm.ReadUInt32(nSig1).ReadUInt32(nSig2);

    if (!rIStm.good() || rIStm.Tell() - nStartPos != size || nSig1 != TOD_SIG1 || nSig2 != TOD_SIG2)
        return;

    rObjDesc.maClassName = className;
    rObjDesc.mnViewAspect = viewAspect;
    rObjDesc.maSize = Size(width, height);
    rObjDesc.maDragStartPos = Point(dragStartPosX, dragStartPosY);
    rObjDesc.maTypeName = typeName;
    rObjDesc.maDisplayName = displayName;
}

// the reading of the parameter is done using the special service css::datatransfer::MimeContentType,
// a similar approach should be implemented for creation of the mimetype string;
// for now the set of acceptable characters has to be hardcoded, in future it should be part of the service that creates the mimetype

static OUString ImplGetParameterString( const TransferableObjectDescriptor& rObjDesc )
{
    const OUString   aClassName( rObjDesc.maClassName.GetHexName() );
    OUString         aParams;

    if( !aClassName.isEmpty() )
    {
        aParams += ";classname=\"" + aClassName + "\"";
    }

    if( !rObjDesc.maTypeName.isEmpty() )
    {
        aParams += ";typename=\""  + rObjDesc.maTypeName + "\"";
    }

    if( !rObjDesc.maDisplayName.isEmpty() )
    {
        // the display name might contain unacceptable characters, encode all of them
        // this seems to be the only parameter currently that might contain such characters
        static constexpr auto pToAccept = rtl::createUriCharClass(
            u8"()<>@,;:/[]?=!#$&'*+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}~. ");

        aParams += ";displayname=\""
            + rtl::Uri::encode(
                rObjDesc.maDisplayName, pToAccept.data(), rtl_UriEncodeIgnoreEscapes,
                RTL_TEXTENCODING_UTF8)
            + "\"";
    }

    aParams += ";viewaspect=\"" + OUString::number(rObjDesc.mnViewAspect)
        + "\";width=\"" + OUString::number(rObjDesc.maSize.Width())
        + "\";height=\"" + OUString::number(rObjDesc.maSize.Height())
        + "\";posx=\"" + OUString::number(rObjDesc.maDragStartPos.X())
        + "\";posy=\"" + OUString::number(rObjDesc.maDragStartPos.X()) + "\"";

    return aParams;
}


static void ImplSetParameterString( TransferableObjectDescriptor& rObjDesc, const DataFlavorEx& rFlavorEx )
{
    const Reference< XComponentContext >&       xContext( ::comphelper::getProcessComponentContext() );

    try
    {
        Reference< XMimeContentTypeFactory >  xMimeFact = MimeContentTypeFactory::create( xContext );

        Reference< XMimeContentType > xMimeType( xMimeFact->createMimeContentType( rFlavorEx.MimeType ) );

        if( xMimeType.is() )
        {
            static constexpr OUString aClassNameString( u"classname"_ustr );
            static constexpr OUString aTypeNameString( u"typename"_ustr );
            static constexpr OUString aDisplayNameString( u"displayname"_ustr );
            static constexpr OUString aViewAspectString( u"viewaspect"_ustr );
            static constexpr OUString aWidthString( u"width"_ustr );
            static constexpr OUString aHeightString( u"height"_ustr );
            static constexpr OUString aPosXString( u"posx"_ustr );
            static constexpr OUString aPosYString( u"posy"_ustr );

            if( xMimeType->hasParameter( aClassNameString ) )
            {
                rObjDesc.maClassName.MakeId( xMimeType->getParameterValue( aClassNameString ) );
            }

            if( xMimeType->hasParameter( aTypeNameString ) )
            {
                rObjDesc.maTypeName = xMimeType->getParameterValue( aTypeNameString );
            }

            if( xMimeType->hasParameter( aDisplayNameString ) )
            {
                // the display name might contain unacceptable characters, in this case they should be encoded
                // this seems to be the only parameter currently that might contain such characters
                rObjDesc.maDisplayName = ::rtl::Uri::decode( xMimeType->getParameterValue( aDisplayNameString ), rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
            }

            if( xMimeType->hasParameter( aViewAspectString ) )
            {
                rObjDesc.mnViewAspect = static_cast< sal_uInt16 >( xMimeType->getParameterValue( aViewAspectString ).toInt32() );
            }

            if( xMimeType->hasParameter( aWidthString ) )
            {
                rObjDesc.maSize.setWidth( xMimeType->getParameterValue( aWidthString ).toInt32() );
            }

            if( xMimeType->hasParameter( aHeightString ) )
            {
                rObjDesc.maSize.setHeight( xMimeType->getParameterValue( aHeightString ).toInt32() );
            }

            if( xMimeType->hasParameter( aPosXString ) )
            {
                rObjDesc.maDragStartPos.setX( xMimeType->getParameterValue( aPosXString ).toInt32() );
            }

            if( xMimeType->hasParameter( aPosYString ) )
            {
                rObjDesc.maDragStartPos.setY( xMimeType->getParameterValue( aPosYString ).toInt32() );
            }
        }
    }
    catch( const css::uno::Exception& )
    {
    }
}


TransferableHelper::TerminateListener::TerminateListener( TransferableHelper& rTransferableHelper ) :
    mrParent( rTransferableHelper )
{
}


TransferableHelper::TerminateListener::~TerminateListener()
{
}


void SAL_CALL TransferableHelper::TerminateListener::disposing( const EventObject& )
{
}


void SAL_CALL TransferableHelper::TerminateListener::queryTermination( const EventObject& )
{
}


void SAL_CALL TransferableHelper::TerminateListener::notifyTermination( const EventObject& )
{
    mrParent.ImplFlush();
    mrParent.mxTerminateListener.clear();
}

OUString SAL_CALL TransferableHelper::TerminateListener::getImplementationName()
{
    return u"com.sun.star.comp.svt.TransferableHelperTerminateListener"_ustr;
}

sal_Bool SAL_CALL TransferableHelper::TerminateListener::supportsService(const OUString& /*rServiceName*/)
{
    return false;
}

css::uno::Sequence<OUString> TransferableHelper::TerminateListener::getSupportedServiceNames()
{
    return {};
}

TransferableHelper::~TransferableHelper()
{
    rtl::Reference< TerminateListener > listener;
    {
        const SolarMutexGuard aGuard;
        std::swap(listener, mxTerminateListener);
    }
    if (listener.is()) {
        Desktop::create(comphelper::getProcessComponentContext())->removeTerminateListener(
            listener);
    }
}

Any SAL_CALL TransferableHelper::getTransferData( const DataFlavor& rFlavor )
{
    return getTransferData2(rFlavor, OUString());
}

Any SAL_CALL TransferableHelper::getTransferData2( const DataFlavor& rFlavor, const OUString& rDestDoc )
{
    if( !maAny.hasValue() || maFormats.empty() || ( maLastFormat != rFlavor.MimeType ) )
    {
        const SolarMutexGuard aGuard;

        maLastFormat = rFlavor.MimeType;
        maAny = Any();

        try
        {
            DataFlavor  aSubstFlavor;
            bool        bDone = false;

            // add formats if not already done
            if (maFormats.empty())
                AddSupportedFormats();

            // check alien formats first and try to get a substitution format
            if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aSubstFlavor ) &&
                TransferableDataHelper::IsEqual( aSubstFlavor, rFlavor ) )
            {
                GetData(aSubstFlavor, rDestDoc);
                bDone = maAny.hasValue();
            }
            else if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::BMP, aSubstFlavor )
                && TransferableDataHelper::IsEqual( aSubstFlavor, rFlavor )
                && SotExchange::GetFormatDataFlavor(SotClipboardFormatId::BITMAP, aSubstFlavor))
            {
                GetData(aSubstFlavor, rDestDoc);
                bDone = true;
            }
            else if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EMF, aSubstFlavor ) &&
                     TransferableDataHelper::IsEqual( aSubstFlavor, rFlavor ) &&
                     SotExchange::GetFormatDataFlavor( SotClipboardFormatId::GDIMETAFILE, aSubstFlavor ) )
            {
                GetData(aSubstFlavor, rDestDoc);

                if( maAny.hasValue() )
                {
                    Sequence< sal_Int8 > aSeq;

                    if( maAny >>= aSeq )
                    {
                        GDIMetaFile     aMtf;
                        {
                            SvMemoryStream aSrcStm( aSeq.getArray(), aSeq.getLength(), StreamMode::WRITE | StreamMode::TRUNC );
                            SvmReader aReader( aSrcStm );
                            aReader.Read( aMtf );
                        }

                        Graphic         aGraphic( aMtf );
                        SvMemoryStream  aDstStm( 65535, 65535 );

                        if( GraphicConverter::Export( aDstStm, aGraphic, ConvertDataFormat::EMF ) == ERRCODE_NONE )
                        {
                            maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aDstStm.GetData() ),
                                                            aDstStm.TellEnd() );
                            bDone = true;
                        }
                    }
                }
            }
            else if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::WMF, aSubstFlavor ) &&
                     TransferableDataHelper::IsEqual( aSubstFlavor, rFlavor ) &&
                     SotExchange::GetFormatDataFlavor( SotClipboardFormatId::GDIMETAFILE, aSubstFlavor ) )
            {
                GetData(aSubstFlavor, rDestDoc);

                if( maAny.hasValue() )
                {
                    Sequence< sal_Int8 > aSeq;

                    if( maAny >>= aSeq )
                    {
                        GDIMetaFile     aMtf;
                        {
                            SvMemoryStream aSrcStm( aSeq.getArray(), aSeq.getLength(), StreamMode::WRITE | StreamMode::TRUNC );
                            SvmReader aReader( aSrcStm );
                            aReader.Read( aMtf );
                        }

                        SvMemoryStream  aDstStm( 65535, 65535 );

                        // taking wmf without file header
                        if ( ConvertGDIMetaFileToWMF( aMtf, aDstStm, nullptr, false ) )
                        {
                            maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aDstStm.GetData() ),
                                                            aDstStm.TellEnd() );
                            bDone = true;
                        }
                    }
                }
            }
            else if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::SVG, aSubstFlavor ) &&
                     TransferableDataHelper::IsEqual( aSubstFlavor, rFlavor ) &&
                     SotExchange::GetFormatDataFlavor( SotClipboardFormatId::GDIMETAFILE, aSubstFlavor ) )
            {
                GetData(aSubstFlavor, rDestDoc);

                if( maAny.hasValue() )
                {
                    Sequence< sal_Int8 > aSeq;

                    if( maAny >>= aSeq )
                    {
                        GDIMetaFile     aMtf;
                        {
                            SvMemoryStream aSrcStm( aSeq.getArray(), aSeq.getLength(), StreamMode::WRITE | StreamMode::TRUNC );
                            SvmReader aReader( aSrcStm );
                            aReader.Read( aMtf );
                        }

                        SvMemoryStream  aDstStm( 65535, 65535 );
                        Graphic         aGraphic( aMtf );

                        if( GraphicConverter::Export( aDstStm, aGraphic, ConvertDataFormat::SVG ) == ERRCODE_NONE )
                        {
                            maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aDstStm.GetData() ),
                                                            aDstStm.TellEnd() );
                            bDone = true;
                        }
                    }
                }
            }

            // reset Any if substitute doesn't work
            if( !bDone && maAny.hasValue() )
                maAny = Any();

            // if any is not yet filled, use standard format
            if( !maAny.hasValue() )
                GetData(rFlavor, rDestDoc);
        }
        catch( const css::uno::Exception& )
        {
        }

        if( !maAny.hasValue() )
            throw UnsupportedFlavorException();
    }

    return maAny;
}

sal_Bool SAL_CALL TransferableHelper::isComplex()
{
    // By default everything is complex, until proven otherwise
    // in the respective document type transferable handler.
    return true;
}

Sequence< DataFlavor > SAL_CALL TransferableHelper::getTransferDataFlavors()
{
    const SolarMutexGuard aGuard;

    try
    {
        if(maFormats.empty())
            AddSupportedFormats();
    }
    catch( const css::uno::Exception& )
    {
    }

    return comphelper::containerToSequence<DataFlavor>(maFormats);
}


sal_Bool SAL_CALL TransferableHelper::isDataFlavorSupported( const DataFlavor& rFlavor )
{
    const SolarMutexGuard aGuard;

    try
    {
        if (maFormats.empty())
            AddSupportedFormats();
    }
    catch( const css::uno::Exception& )
    {
    }

    for (auto const& format : maFormats)
    {
        if( TransferableDataHelper::IsEqual( format, rFlavor ) )
        {
            return true;
        }
    }

    return false;
}


void SAL_CALL TransferableHelper::lostOwnership( const Reference< XClipboard >&, const Reference< XTransferable >& )
{
    const SolarMutexGuard aGuard;

    try
    {
        if( mxTerminateListener.is() )
        {
            Reference< XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
            xDesktop->removeTerminateListener( mxTerminateListener );

            mxTerminateListener.clear();
        }

        ObjectReleased();
    }
    catch( const css::uno::Exception& )
    {
    }
}


void SAL_CALL TransferableHelper::disposing( const EventObject& )
{
}


void SAL_CALL TransferableHelper::dragDropEnd( const DragSourceDropEvent& rDSDE )
{
    const SolarMutexGuard aGuard;

    try
    {
        DragFinished( rDSDE.DropSuccess ? ( rDSDE.DropAction & ~DNDConstants::ACTION_DEFAULT ) : DNDConstants::ACTION_NONE );
        ObjectReleased();
    }
    catch( const css::uno::Exception& )
    {
    }
}


void SAL_CALL TransferableHelper::dragEnter( const DragSourceDragEvent& )
{
}


void SAL_CALL TransferableHelper::dragExit( const DragSourceEvent& )
{
}


void SAL_CALL TransferableHelper::dragOver( const DragSourceDragEvent& )
{
}


void SAL_CALL TransferableHelper::dropActionChanged( const DragSourceDragEvent& )
{
}


void TransferableHelper::ImplFlush()
{
    if( !mxClipboard.is() )
        return;

    Reference< XFlushableClipboard >    xFlushableClipboard( mxClipboard, UNO_QUERY );
    SolarMutexReleaser aReleaser;

    try
    {
        if( xFlushableClipboard.is() )
             xFlushableClipboard->flushClipboard();
    }
    catch( const css::uno::Exception& )
    {
        OSL_FAIL( "Could not flush clipboard" );
    }
}


void TransferableHelper::AddFormat( SotClipboardFormatId nFormat )
{
    DataFlavor aFlavor;

    if( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) )
        AddFormat( aFlavor );
}


void TransferableHelper::AddFormat( const DataFlavor& rFlavor )
{
    bool bAdd = true;

    for (auto & format : maFormats)
    {
        if( TransferableDataHelper::IsEqual( format, rFlavor ) )
        {
            // update MimeType for SotClipboardFormatId::OBJECTDESCRIPTOR in every case
            if ((SotClipboardFormatId::OBJECTDESCRIPTOR == format.mnSotId) && mxObjDesc)
            {
                DataFlavor aObjDescFlavor;

                SotExchange::GetFormatDataFlavor( SotClipboardFormatId::OBJECTDESCRIPTOR, aObjDescFlavor );
                format.MimeType = aObjDescFlavor.MimeType;
                format.MimeType += ::ImplGetParameterString(*mxObjDesc);
            }

            bAdd = false;
            break;
        }
    }

    if( !bAdd )
        return;

    DataFlavorEx   aFlavorEx;

    aFlavorEx.MimeType = rFlavor.MimeType;
    aFlavorEx.HumanPresentableName = rFlavor.HumanPresentableName;
    aFlavorEx.DataType = rFlavor.DataType;
    aFlavorEx.mnSotId = SotExchange::RegisterFormat( rFlavor );

    if ((SotClipboardFormatId::OBJECTDESCRIPTOR == aFlavorEx.mnSotId) && mxObjDesc)
        aFlavorEx.MimeType += ::ImplGetParameterString(*mxObjDesc);

    maFormats.push_back(aFlavorEx);

    if( SotClipboardFormatId::BITMAP == aFlavorEx.mnSotId )
    {
        AddFormat( SotClipboardFormatId::PNG );
        AddFormat( SotClipboardFormatId::BMP );
    }
    else if( SotClipboardFormatId::GDIMETAFILE == aFlavorEx.mnSotId )
    {
        AddFormat( SotClipboardFormatId::EMF );
        AddFormat( SotClipboardFormatId::WMF );
        AddFormat( SotClipboardFormatId::SVG );
    }
}


void TransferableHelper::RemoveFormat( SotClipboardFormatId nFormat )
{
    DataFlavor aFlavor;

    if( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) )
        RemoveFormat( aFlavor );
}


void TransferableHelper::RemoveFormat( const DataFlavor& rFlavor )
{
    DataFlavorExVector::iterator aIter(maFormats.begin());

    while (aIter != maFormats.end())
    {
        if( TransferableDataHelper::IsEqual( *aIter, rFlavor ) )
            aIter = maFormats.erase(aIter);
        else
            ++aIter;
    }
}


bool TransferableHelper::HasFormat( SotClipboardFormatId nFormat )
{
    return std::any_of(maFormats.begin(), maFormats.end(),
              [&](const DataFlavorEx& data) { return data.mnSotId == nFormat; });
}


void TransferableHelper::ClearFormats()
{
    maFormats.clear();
    maAny.clear();
}


bool TransferableHelper::SetAny( const Any& rAny )
{
    maAny = rAny;
    return maAny.hasValue();
}


bool TransferableHelper::SetString( const OUString& rString )
{
    maAny <<= rString;
    return maAny.hasValue();
}

bool TransferableHelper::SetBitmap(const Bitmap& rBitmap, const DataFlavor& rFlavor)
{
    if( !rBitmap.IsEmpty() )
    {
        SvMemoryStream aMemStm( 65535, 65535 );

        if(rFlavor.MimeType.equalsIgnoreAsciiCase("image/png"))
        {
            // write a PNG
            css::uno::Sequence<css::beans::PropertyValue> aFilterData;

#ifdef IOS
            // Use faster compression on slow devices
            aFilterData.realloc(aFilterData.getLength() + 1);
            aFilterData.getArray()[aFilterData.getLength() - 1].Name = "Compression";

            // We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed. For a
            // typical 15 megapixel image from a DSLR, we are talking about a difference of 17 s for
            // the default compression level vs 4 s for best speed, on an iPad Pro from 2017.
            //
            // Sure, the best would be to not have to re-encode the image at all, but have access to
            // the original JPEG or PNG when there is a such.

            aFilterData.getArray()[aFilterData.getLength() - 1].Value <<= 1;
#endif
            vcl::PngImageWriter aPNGWriter(aMemStm);
            aPNGWriter.setParameters(aFilterData);
            aPNGWriter.write(rBitmap);
        }
        else
        {
            // explicitly use Bitmap::Write with bCompressed = sal_False and bFileHeader = sal_True
            WriteDIB(rBitmap, aMemStm, false, true);
        }

        maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() );
    }

    return maAny.hasValue();
}


bool TransferableHelper::SetGDIMetaFile( const GDIMetaFile& rMtf )
{
    if( rMtf.GetActionSize() )
    {
        SvMemoryStream aMemStm( 65535, 65535 );

        SvmWriter aWriter( aMemStm );
        aWriter.Write( rMtf );
        maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() );
    }

    return maAny.hasValue();
}


bool TransferableHelper::SetGraphic( const Graphic& rGraphic )
{
    if( rGraphic.GetType() != GraphicType::NONE )
    {
        SvMemoryStream aMemStm( 65535, 65535 );

        aMemStm.SetVersion( SOFFICE_FILEFORMAT_50 );
        aMemStm.SetCompressMode( SvStreamCompressFlags::NATIVE );

        TypeSerializer aSerializer(aMemStm);
        aSerializer.writeGraphic(rGraphic);

        maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() );
    }

    return maAny.hasValue();
}


bool TransferableHelper::SetImageMap( const ImageMap& rIMap )
{
    SvMemoryStream aMemStm( 8192, 8192 );

    aMemStm.SetVersion( SOFFICE_FILEFORMAT_50 );
    rIMap.Write( aMemStm );
    maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() );

    return maAny.hasValue();
}


bool TransferableHelper::SetTransferableObjectDescriptor( const TransferableObjectDescriptor& rDesc )
{
    PrepareOLE( rDesc );

    SvMemoryStream aMemStm( 1024, 1024 );

    WriteTransferableObjectDescriptor( aMemStm, rDesc );
    maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.Tell() );

    return maAny.hasValue();
 }


bool TransferableHelper::SetINetBookmark( const INetBookmark& rBmk,
                                          const css::datatransfer::DataFlavor& rFlavor )
{
    rtl_TextEncoding eSysCSet = osl_getThreadTextEncoding();

    switch( SotExchange::GetFormat( rFlavor ) )
    {
        case SotClipboardFormatId::SOLK:
        {
            OString sURL(OUStringToOString(rBmk.GetURL(), eSysCSet));
            OString sDesc(OUStringToOString(rBmk.GetDescription(), eSysCSet));
            OString sOut =
                OString::number(sURL.getLength())
                + "@" + sURL
                + OString::number(sDesc.getLength())
                + "@" + sDesc;

            Sequence< sal_Int8 > aSeq(sOut.getLength());
            memcpy(aSeq.getArray(), sOut.getStr(), sOut.getLength());
            maAny <<= aSeq;
        }
        break;

        case SotClipboardFormatId::STRING:
        case SotClipboardFormatId::UNIFORMRESOURCELOCATOR:
            maAny <<= rBmk.GetURL();
            break;

        case SotClipboardFormatId::NETSCAPE_BOOKMARK:
        {
            Sequence< sal_Int8 > aSeq( 2048 );
            char* pSeq = reinterpret_cast< char* >( aSeq.getArray() );

            // strncpy fills the rest with nulls, as we need
            strncpy( pSeq, OUStringToOString(rBmk.GetURL(), eSysCSet).getStr(), 1024 );
            strncpy( pSeq + 1024, OUStringToOString(rBmk.GetDescription(), eSysCSet).getStr(), 1024 );

            maAny <<= aSeq;
        }
        break;

#ifdef _WIN32
        case SotClipboardFormatId::FILEGRPDESCRIPTOR:
        {
            Sequence< sal_Int8 >    aSeq( sizeof( FILEGROUPDESCRIPTORW ) );
            FILEGROUPDESCRIPTORW*   pFDesc = reinterpret_cast<FILEGROUPDESCRIPTORW*>(aSeq.getArray());
            FILEDESCRIPTORW&        rFDesc1 = pFDesc->fgd[ 0 ];

            pFDesc->cItems = 1;
            memset( &rFDesc1, 0, sizeof( rFDesc1 ) );
            rFDesc1.dwFlags = FD_LINKUI;

            OUStringBuffer aStr(rBmk.GetDescription());
            for( size_t nChar = 0; (nChar = std::u16string_view(aStr).find_first_of(u"\\/:*?\"<>|"sv, nChar)) != std::u16string_view::npos; )
                aStr.remove(nChar, 1);

            aStr.insert(0, "Shortcut to ");
            aStr.append(".URL");
            wcscpy( rFDesc1.cFileName, o3tl::toW(aStr.getStr()) );

            maAny <<= aSeq;
        }
        break;

        case SotClipboardFormatId::FILECONTENT:
        {
            maAny <<= "[InternetShortcut]\x0aURL=" + rBmk.GetURL();
        }
        break;
#endif

        default:
        break;
    }

    return maAny.hasValue();
}


bool TransferableHelper::SetINetImage( const INetImage& rINtImg,
                                       const css::datatransfer::DataFlavor& rFlavor )
{
    SvMemoryStream aMemStm( 1024, 1024 );

    aMemStm.SetVersion( SOFFICE_FILEFORMAT_50 );
    rINtImg.Write( aMemStm, SotExchange::GetFormat( rFlavor ) );

    maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() );

    return maAny.hasValue();
}


bool TransferableHelper::SetObject( void* pUserObject, sal_uInt32 nUserObjectId, const DataFlavor& rFlavor )
{
    SvMemoryStream aStm;

    aStm.SetVersion( SOFFICE_FILEFORMAT_50 );

    if( pUserObject && WriteObject( aStm, pUserObject, nUserObjectId, rFlavor ) )
    {
        const sal_uInt32        nLen = aStm.TellEnd();
        Sequence< sal_Int8 >    aSeq( nLen );

        aStm.Seek( STREAM_SEEK_TO_BEGIN );
        aStm.ReadBytes(aSeq.getArray(), nLen);

        if( nLen && ( SotExchange::GetFormat( rFlavor ) == SotClipboardFormatId::STRING ) )
        {
            //JP 24.7.2001: as I know was this only for the writer application and this
            //              writes now UTF16 format into the stream
            //JP 6.8.2001:  and now it writes UTF8 because then exist no problem with
            //              little / big endians! - Bug 88121
            maAny <<= OUString( reinterpret_cast< const char* >( aSeq.getConstArray() ), nLen - 1, RTL_TEXTENCODING_UTF8 );
        }
        else
            maAny <<= aSeq;
    }

    return maAny.hasValue();
}


bool TransferableHelper::WriteObject( SvStream&, void*, sal_uInt32, const DataFlavor& )
{
    OSL_FAIL( "TransferableHelper::WriteObject( ... ) not implemented" );
    return false;
}


void TransferableHelper::DragFinished( sal_Int8 )
{
}


void TransferableHelper::ObjectReleased()
{
}


void TransferableHelper::PrepareOLE( const TransferableObjectDescriptor& rObjDesc )
{
    mxObjDesc.reset(new TransferableObjectDescriptor(rObjDesc));

    if( HasFormat( SotClipboardFormatId::OBJECTDESCRIPTOR ) )
        AddFormat( SotClipboardFormatId::OBJECTDESCRIPTOR );
}

void TransferableHelper::CopyToClipboard(const Reference<XClipboard>& rClipboard)
{
    if( rClipboard.is() )
        mxClipboard = rClipboard;

    if( !mxClipboard.is() || mxTerminateListener.is() )
        return;

    try
    {
        mxTerminateListener = new TerminateListener(*this);
        Reference< XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
        xDesktop->addTerminateListener(mxTerminateListener);

        mxClipboard->setContents(this, this);
    }
    catch( const css::uno::Exception& )
    {
    }
}

void TransferableHelper::CopyToClipboard(vcl::Window* pWindow)
{
    DBG_ASSERT( pWindow, "Window pointer is NULL" );
    Reference< XClipboard > xClipboard;

    if( pWindow )
        xClipboard = pWindow->GetClipboard();

    CopyToClipboard(xClipboard);
}

void TransferableHelper::CopyToSelection(const Reference<XClipboard>& rSelection)
{
    if( !rSelection.is() || mxTerminateListener.is() )
        return;

    try
    {
        mxTerminateListener = new TerminateListener(*this);
        Reference< XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
        xDesktop->addTerminateListener(mxTerminateListener);

        rSelection->setContents(this, this);
    }
    catch( const css::uno::Exception& )
    {
    }
}

void TransferableHelper::CopyToPrimarySelection()
{
    CopyToSelection(GetSystemPrimarySelection());
}

void TransferableHelper::StartDrag( vcl::Window* pWindow, sal_Int8 nDnDSourceActions )
{
    assert(pWindow && "Window pointer is NULL");
    Reference< XDragSource > xDragSource( pWindow->GetDragSource() );

    if( !xDragSource.is() )
        return;

    /*
     *    #96792# release mouse before actually starting DnD.
     *    This is necessary for the X11 DnD implementation to work.
     */
    if( pWindow->IsMouseCaptured() )
        pWindow->ReleaseMouse();

    const Point aPt( pWindow->GetPointerPosPixel() );

    // On macOS we are forced to execute 'startDrag' synchronously
    // contrary to the XDragSource interface specification because
    // we can receive drag events from the system only in the main
    // thread
#if !defined(MACOSX)
    SolarMutexReleaser aReleaser;
#endif

    try
    {
        DragGestureEvent    aEvt;
        aEvt.DragAction = DNDConstants::ACTION_COPY;
        aEvt.DragOriginX = aPt.X();
        aEvt.DragOriginY = aPt.Y();
        aEvt.DragSource = xDragSource;

        xDragSource->startDrag( aEvt, nDnDSourceActions, DND_POINTER_NONE, DND_IMAGE_NONE, this, this );
    }
    catch( const css::uno::Exception& )
    {
    }
}

void TransferableHelper::ClearPrimarySelection()
{
    Reference< XClipboard > xSelection(GetSystemPrimarySelection());

    if( xSelection.is() )
        xSelection->setContents( nullptr, nullptr );
}

class TransferableClipboardNotifier : public ::cppu::WeakImplHelper< XClipboardListener >
{
private:
    Reference< XClipboardNotifier > mxNotifier;
    TransferableDataHelper*         mpListener;

protected:
    // XClipboardListener
    virtual void SAL_CALL changedContents( const clipboard::ClipboardEvent& event ) override;

    // XEventListener
    virtual void SAL_CALL disposing( const EventObject& Source ) override;

public:
    TransferableClipboardNotifier( const Reference< XClipboard >& _rxClipboard, TransferableDataHelper& _rListener );

    /// determines whether we're currently listening
    bool isListening() const { return mpListener != nullptr; }

    /// makes the instance non-functional
    void    dispose();
};


TransferableClipboardNotifier::TransferableClipboardNotifier( const Reference< XClipboard >& _rxClipboard, TransferableDataHelper& _rListener )
    :mxNotifier( _rxClipboard, UNO_QUERY )
    ,mpListener( &_rListener )
{
    osl_atomic_increment( &m_refCount );
    {
        if ( mxNotifier.is() )
            mxNotifier->addClipboardListener( this );
        else
            // born dead
            mpListener = nullptr;
    }
    osl_atomic_decrement( &m_refCount );
}


void SAL_CALL TransferableClipboardNotifier::changedContents( const clipboard::ClipboardEvent& event )
{
    SolarMutexGuard aSolarGuard;
    if( mpListener )
        mpListener->Rebind( event.Contents );
}


void SAL_CALL TransferableClipboardNotifier::disposing( const EventObject& )
{
    // clipboard is being disposed. Hmm. Okay, become disfunctional myself.
    dispose();
}


void TransferableClipboardNotifier::dispose()
{
    SolarMutexGuard g;

    Reference< XClipboardListener > xKeepMeAlive( this );

    if ( mxNotifier.is() )
        mxNotifier->removeClipboardListener( this );
    mxNotifier.clear();

    mpListener = nullptr;
}

TransferableDataHelper::TransferableDataHelper()
    : mxObjDesc(new TransferableObjectDescriptor)
{
}

TransferableDataHelper::TransferableDataHelper(const Reference< css::datatransfer::XTransferable >& rxTransferable)
    : mxTransfer(rxTransferable)
    , mxObjDesc(new TransferableObjectDescriptor)
{
    InitFormats();
}

TransferableDataHelper::TransferableDataHelper(const TransferableDataHelper& rDataHelper)
    : mxTransfer(rDataHelper.mxTransfer)
    , mxClipboard(rDataHelper.mxClipboard)
    , maFormats(rDataHelper.maFormats)
    , mxObjDesc(new TransferableObjectDescriptor(*rDataHelper.mxObjDesc))
{
}

TransferableDataHelper::TransferableDataHelper(TransferableDataHelper&& rDataHelper) noexcept
    : mxTransfer(std::move(rDataHelper.mxTransfer))
    , mxClipboard(std::move(rDataHelper.mxClipboard))
    , maFormats(std::move(rDataHelper.maFormats))
    , mxObjDesc(std::move(rDataHelper.mxObjDesc))
{
}

TransferableDataHelper& TransferableDataHelper::operator=( const TransferableDataHelper& rDataHelper )
{
    if ( this != &rDataHelper )
    {
        SolarMutexGuard g;

        const bool bWasClipboardListening = mxClipboardListener.is();

        if (bWasClipboardListening)
            StopClipboardListening();

        mxTransfer = rDataHelper.mxTransfer;
        maFormats = rDataHelper.maFormats;
        mxObjDesc.reset(new TransferableObjectDescriptor(*rDataHelper.mxObjDesc));
        mxClipboard = rDataHelper.mxClipboard;

        if (bWasClipboardListening)
            StartClipboardListening();
    }

    return *this;
}

TransferableDataHelper& TransferableDataHelper::operator=(TransferableDataHelper&& rDataHelper)
{
    SolarMutexGuard g;

    const bool bWasClipboardListening = mxClipboardListener.is();

    if (bWasClipboardListening)
        StopClipboardListening();

    mxTransfer = std::move(rDataHelper.mxTransfer);
    maFormats = std::move(rDataHelper.maFormats);
    mxObjDesc = std::move(rDataHelper.mxObjDesc);
    mxClipboard = std::move(rDataHelper.mxClipboard);

    if (bWasClipboardListening)
        StartClipboardListening();

    return *this;
}

TransferableDataHelper::~TransferableDataHelper()
{
    StopClipboardListening( );
    {
        SolarMutexGuard g;
        maFormats.clear();
        mxObjDesc.reset();
    }
}

void TransferableDataHelper::FillDataFlavorExVector( const Sequence< DataFlavor >& rDataFlavorSeq,
                                                     DataFlavorExVector& rDataFlavorExVector )
{
    try
    {
        const Reference< XComponentContext >&          xContext( ::comphelper::getProcessComponentContext() );
        Reference< XMimeContentTypeFactory >    xMimeFact = MimeContentTypeFactory::create( xContext );
        DataFlavorEx                            aFlavorEx;
        static constexpr OUString        aCharsetStr( u"charset"_ustr );


        for (auto const& rFlavor : rDataFlavorSeq)
        {
            Reference< XMimeContentType >   xMimeType;

            try
            {
                if( !rFlavor.MimeType.isEmpty() )
                    xMimeType = xMimeFact->createMimeContentType( rFlavor.MimeType );
            }
            catch( const css::uno::Exception& )
            {
            }

            aFlavorEx.MimeType = rFlavor.MimeType;
            aFlavorEx.HumanPresentableName = rFlavor.HumanPresentableName;
            aFlavorEx.DataType = rFlavor.DataType;
            aFlavorEx.mnSotId = SotExchange::RegisterFormat( rFlavor );

            rDataFlavorExVector.push_back( aFlavorEx );

            // add additional formats for special mime types
            if(SotClipboardFormatId::BMP == aFlavorEx.mnSotId || SotClipboardFormatId::PNG == aFlavorEx.mnSotId || SotClipboardFormatId::JPEG == aFlavorEx.mnSotId)
            {
                if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::BITMAP, aFlavorEx ) )
                {
                    aFlavorEx.mnSotId = SotClipboardFormatId::BITMAP;
                    rDataFlavorExVector.push_back( aFlavorEx );
                }
            }
            else if( SotClipboardFormatId::WMF == aFlavorEx.mnSotId
                     || SotClipboardFormatId::EMF == aFlavorEx.mnSotId
                     || SotClipboardFormatId::SVG == aFlavorEx.mnSotId )
            {
                if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::GDIMETAFILE, aFlavorEx ) )
                {
                    aFlavorEx.mnSotId = SotClipboardFormatId::GDIMETAFILE;
                    rDataFlavorExVector.push_back( aFlavorEx );
                }
            }
            else if ( SotClipboardFormatId::HTML_SIMPLE == aFlavorEx.mnSotId  )
            {
                // #104735# HTML_SIMPLE may also be inserted without comments
                aFlavorEx.mnSotId = SotClipboardFormatId::HTML_NO_COMMENT;
                rDataFlavorExVector.push_back( aFlavorEx );
            }
            else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/plain" ) )
            {
                // add, if it is a UTF-8 byte buffer
                if( xMimeType->hasParameter( aCharsetStr ) )
                {
                    if( xMimeType->getParameterValue( aCharsetStr ).equalsIgnoreAsciiCase( "unicode" ) ||
                        xMimeType->getParameterValue( aCharsetStr ).equalsIgnoreAsciiCase( "utf-16" ) )
                    {
                        rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::STRING;

                    }
                }
            }
            else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/rtf" ) )
            {
                rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::RTF;
            }
            else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/richtext" ) )
            {
                rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::RICHTEXT;
            }
            else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/html" ) )

            {
                rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::HTML;
            }
            else if(xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/markdown "))
            {
                rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::MARKDOWN;
            }
            else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/uri-list" ) )
            {
                rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::FILE_LIST;
            }
            else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "application/x-openoffice-objectdescriptor-xml" ) )
            {
                rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::OBJECTDESCRIPTOR;
            }
        }
    }
    catch( const css::uno::Exception& )
    {
    }
}

void TransferableDataHelper::InitFormats()
{
    SolarMutexGuard aSolarGuard;

    maFormats.clear();
    mxObjDesc.reset(new TransferableObjectDescriptor);

    if( !mxTransfer.is() )
        return;

    TransferableDataHelper::FillDataFlavorExVector(mxTransfer->getTransferDataFlavors(), maFormats);

    for (auto const& format : maFormats)
    {
        if( SotClipboardFormatId::OBJECTDESCRIPTOR == format.mnSotId )
        {
            ImplSetParameterString(*mxObjDesc, format);
            auto data = GetSequence(format, {});
            SvMemoryStream aSrcStm(data.getArray(), data.getLength(), StreamMode::READ);
            TryReadTransferableObjectDescriptor(aSrcStm, *mxObjDesc);
            break;
        }
    }
}


bool TransferableDataHelper::HasFormat( SotClipboardFormatId nFormat ) const
{
    SolarMutexGuard g;
    return std::any_of(maFormats.begin(), maFormats.end(),
              [&](const DataFlavorEx& data) { return data.mnSotId == nFormat; });
}

bool TransferableDataHelper::HasFormat( const DataFlavor& rFlavor ) const
{
    SolarMutexGuard g;
    for (auto const& format : maFormats)
    {
        if( TransferableDataHelper::IsEqual( rFlavor, format ) )
            return true;
    }

    return false;
}

sal_uInt32 TransferableDataHelper::GetFormatCount() const
{
    SolarMutexGuard g;
    return maFormats.size();
}

SotClipboardFormatId TransferableDataHelper::GetFormat( sal_uInt32 nFormat ) const
{
    SolarMutexGuard g;
    DBG_ASSERT(nFormat < maFormats.size(), "TransferableDataHelper::GetFormat: invalid format index");
    return( ( nFormat < maFormats.size() ) ? maFormats[ nFormat ].mnSotId : SotClipboardFormatId::NONE );
}

DataFlavor TransferableDataHelper::GetFormatDataFlavor( sal_uInt32 nFormat ) const
{
    SolarMutexGuard g;
    DBG_ASSERT(nFormat < maFormats.size(), "TransferableDataHelper::GetFormat: invalid format index");

    DataFlavor aRet;

    if (nFormat < maFormats.size())
        aRet = maFormats[nFormat];

    return aRet;
}


Reference< XTransferable > TransferableDataHelper::GetXTransferable() const
{
    Reference< XTransferable > xRet;

    if( mxTransfer.is() )
    {
        try
        {
            xRet = mxTransfer;

            // do a dummy call to check, if this interface is valid (nasty)
            xRet->getTransferDataFlavors();

        }
        catch( const css::uno::Exception& )
        {
            xRet.clear();
        }
    }

    return xRet;
}


Any TransferableDataHelper::GetAny( SotClipboardFormatId nFormat, const OUString& rDestDoc ) const
{
    Any aReturn;

    DataFlavor aFlavor;
    if ( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) )
        aReturn = GetAny(aFlavor, rDestDoc);

    return aReturn;
}

Any TransferableDataHelper::GetAny( const DataFlavor& rFlavor, const OUString& rDestDoc ) const
{
    Any aRet;

    try
    {
        SolarMutexGuard g;
        if( mxTransfer.is() )
        {
            const SotClipboardFormatId         nRequestFormat = SotExchange::GetFormat( rFlavor );

            Reference<css::datatransfer::XTransferable2> xTransfer2(mxTransfer, UNO_QUERY);

            if( nRequestFormat != SotClipboardFormatId::NONE )
            {
                // try to get alien format first
                for (auto const& format : maFormats)
                {
                    if( ( nRequestFormat == format.mnSotId ) && !rFlavor.MimeType.equalsIgnoreAsciiCase( format.MimeType ) )
                    {
// tdf#133365: only release solar mutex on Windows
#ifdef _WIN32
                        // Our own thread may handle the nested IDataObject::GetData call,
                        // and try to acquire solar mutex
                        SolarMutexReleaser r;
#endif // _WIN32

                        if (xTransfer2.is())
                            aRet = xTransfer2->getTransferData2(format, rDestDoc);
                        else
                            aRet = mxTransfer->getTransferData(format);
                    }

                    if( aRet.hasValue() )
                        break;
                }
            }

            if( !aRet.hasValue() )
            {
// tdf#133365: only release solar mutex on Windows
#ifdef _WIN32
                // Our own thread may handle the nested IDataObject::GetData call,
                // and try to acquire solar mutex
                SolarMutexReleaser r;
#endif // _WIN32

                if (xTransfer2.is())
                    aRet = xTransfer2->getTransferData2(rFlavor, rDestDoc);
                else
                    aRet = mxTransfer->getTransferData(rFlavor);
            }
        }
    }
    catch( const css::uno::Exception& )
    {
    }

    return aRet;
}


bool TransferableDataHelper::GetString( SotClipboardFormatId nFormat, OUString& rStr ) const
{
    DataFlavor aFlavor;
    return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetString( aFlavor, rStr ) );
}


bool TransferableDataHelper::GetString( const DataFlavor& rFlavor, OUString& rStr ) const
{
    Any aAny = GetAny(rFlavor, OUString());
    bool        bRet = false;

    if( aAny.hasValue() )
    {
        OUString         aOUString;
        Sequence< sal_Int8 >    aSeq;

        if( aAny >>= aOUString )
        {
            rStr = aOUString;
            bRet = true;
        }
        else if( aAny >>= aSeq )
        {

            const char* pChars = reinterpret_cast< const char* >( aSeq.getConstArray() );
            sal_Int32       nLen = aSeq.getLength();

            //JP 10.10.2001: 92930 - don't copy the last zero character into the string.
            //DVO 2002-05-27: strip _all_ trailing zeros
            while( nLen && ( 0 == *( pChars + nLen - 1 ) ) )
                --nLen;

            rStr = OUString( pChars, nLen, osl_getThreadTextEncoding() );
            bRet = true;
        }
    }

    return bRet;
}

bool TransferableDataHelper::GetBitmap(SotClipboardFormatId nFormat, Bitmap& rBmp) const
{
    if(SotClipboardFormatId::BITMAP == nFormat)
    {
        // try to get PNG first
        DataFlavor aFlavor;

        if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::PNG, aFlavor))
        {
            if (GetBitmap(aFlavor, rBmp))
            {
                return true;
            }
        }

        // then JPEG
        if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::JPEG, aFlavor))
        {
            if (GetBitmap(aFlavor, rBmp))
            {
                return true;
            }
        }
    }

    DataFlavor aFlavor;
    return (SotExchange::GetFormatDataFlavor(nFormat, aFlavor) && GetBitmap(aFlavor, rBmp));
}

bool TransferableDataHelper::GetBitmap(const DataFlavor& rFlavor, Bitmap& rBmp) const
{
    std::unique_ptr<SvStream> xStm = GetSotStorageStream(rFlavor);
    DataFlavor aSubstFlavor;
    bool bRet(xStm);
    bool bSuppressPNG(false); // #122982# If PNG stream not accessed, but BMP one, suppress trying to load PNG
    bool bSuppressJPEG(false);

    if(!bRet && HasFormat(SotClipboardFormatId::PNG) && SotExchange::GetFormatDataFlavor(SotClipboardFormatId::PNG, aSubstFlavor))
    {
        // when no direct success, try if PNG is available
        xStm = GetSotStorageStream(aSubstFlavor);
        bRet = bool(xStm);
        bSuppressJPEG = bRet;
    }

    if(!bRet && HasFormat(SotClipboardFormatId::JPEG) && SotExchange::GetFormatDataFlavor(SotClipboardFormatId::JPEG, aSubstFlavor))
    {
        xStm = GetSotStorageStream(aSubstFlavor);
        bRet = bool(xStm);
        bSuppressPNG = bRet;
    }

    if(!bRet && HasFormat(SotClipboardFormatId::BMP) && SotExchange::GetFormatDataFlavor(SotClipboardFormatId::BMP, aSubstFlavor))
    {
        // when no direct success, try if BMP is available
        xStm = GetSotStorageStream(aSubstFlavor);
        bRet = bool(xStm);
        bSuppressPNG = bRet;
        bSuppressJPEG = bRet;
    }

    if(bRet)
    {
        if(!bSuppressPNG && rFlavor.MimeType.equalsIgnoreAsciiCase("image/png"))
        {
            // it's a PNG, import to Bitmap
            vcl::PngImageReader aPNGReader(*xStm);
            rBmp = aPNGReader.read();
        }
        else if(!bSuppressJPEG && rFlavor.MimeType.equalsIgnoreAsciiCase("image/jpeg"))
        {
            // it's a JPEG, import to Bitmap
            GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
            Graphic aGraphic;
            if (rFilter.ImportGraphic(aGraphic, u"", *xStm) == ERRCODE_NONE)
                rBmp = aGraphic.GetBitmap();
        }

        if(rBmp.IsEmpty())
        {
            Bitmap aBitmap;
            AlphaMask aMask;

            // explicitly use Bitmap::Read with bFileHeader = sal_True
            // #i124085# keep DIBV5 for read from clipboard, but should not happen
            ReadDIBV5(aBitmap, aMask, *xStm);

            if(aMask.GetBitmap().IsEmpty())
            {
                rBmp = std::move(aBitmap);
            }
            else
            {
                rBmp = Bitmap(aBitmap, aMask);
            }
        }

        bRet = (ERRCODE_NONE == xStm->GetError() && !rBmp.IsEmpty());

        /* SJ: #110748# At the moment we are having problems with DDB inserted as DIB. The
           problem is, that some graphics are inserted much too big because the nXPelsPerMeter
           and nYPelsPerMeter of the bitmap fileheader isn't including the correct value.
           Due to this reason the following code assumes that bitmaps with a logical size
           greater than 50 cm aren't having the correct mapmode set.

           The following code should be removed if DDBs and DIBs are supported via clipboard
           properly.
        */
        if(bRet)
        {
            const MapMode aMapMode(rBmp.GetPrefMapMode());

            if(MapUnit::MapPixel != aMapMode.GetMapUnit())
            {
                const Size aSize(OutputDevice::LogicToLogic(rBmp.GetPrefSize(), aMapMode, MapMode(MapUnit::Map100thMM)));

                // #i122388# This wrongly corrects in the given case; changing from 5000 100th mm to
                // the described 50 cm (which is 50000 100th mm)
                if((aSize.Width() > 50000) || (aSize.Height() > 50000))
                {
                    rBmp.SetPrefMapMode(MapMode(MapUnit::MapPixel));

                    // #i122388# also adapt size by applying the mew MapMode
                    const Size aNewSize(o3tl::convert(aSize, o3tl::Length::mm100, o3tl::Length::pt));
                    rBmp.SetPrefSize(aNewSize);
                }
            }
        }
    }

    return bRet;
}


bool TransferableDataHelper::GetGDIMetaFile(SotClipboardFormatId nFormat, GDIMetaFile& rMtf, size_t nMaxActions) const
{
    DataFlavor aFlavor;
    return SotExchange::GetFormatDataFlavor(nFormat, aFlavor) &&
        GetGDIMetaFile(aFlavor, rMtf) &&
        (nMaxActions == 0 || rMtf.GetActionSize() < nMaxActions);
}


bool TransferableDataHelper::GetGDIMetaFile( const DataFlavor& rFlavor, GDIMetaFile& rMtf ) const
{
    std::unique_ptr<SvStream> xStm;
    DataFlavor          aSubstFlavor;
    bool                bRet = false;

    if( (xStm = GetSotStorageStream( rFlavor )) )
    {
        SvmReader aReader( *xStm );
        aReader.Read( rMtf );
        bRet = ( xStm->GetError() == ERRCODE_NONE );
    }

    if( !bRet &&
        HasFormat( SotClipboardFormatId::EMF ) &&
        SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EMF, aSubstFlavor ) &&
        (xStm = GetSotStorageStream( aSubstFlavor)) )
    {
        Graphic aGraphic;

        if( GraphicConverter::Import( *xStm, aGraphic ) == ERRCODE_NONE )
        {
            rMtf = aGraphic.GetGDIMetaFile();
            bRet = true;
        }
    }

    if( !bRet &&
        HasFormat( SotClipboardFormatId::WMF ) &&
        SotExchange::GetFormatDataFlavor( SotClipboardFormatId::WMF, aSubstFlavor ) &&
        (xStm = GetSotStorageStream( aSubstFlavor ) ) )
    {
        Graphic aGraphic;

        if( GraphicConverter::Import( *xStm, aGraphic ) == ERRCODE_NONE )
        {
            rMtf = aGraphic.GetGDIMetaFile();
            bRet = true;
        }
    }

    return bRet;
}


bool TransferableDataHelper::GetGraphic( SotClipboardFormatId nFormat, Graphic& rGraphic ) const
{
    if(SotClipboardFormatId::BITMAP == nFormat)
    {
        // try to get PNG first
        DataFlavor aFlavor;

        if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::PNG, aFlavor))
        {
            if(GetGraphic(aFlavor, rGraphic))
            {
                return true;
            }
        }
    }

    DataFlavor aFlavor;
    return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetGraphic( aFlavor, rGraphic ) );
}


bool TransferableDataHelper::GetGraphic( const css::datatransfer::DataFlavor& rFlavor, Graphic& rGraphic ) const
{
    DataFlavor  aFlavor;
    bool        bRet = false;

    if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::PNG, aFlavor) &&
        TransferableDataHelper::IsEqual(aFlavor, rFlavor))
    {
        // try to get PNG first
        Bitmap aBmp;

        bRet = GetBitmap(aFlavor, aBmp);
        if( bRet )
            rGraphic = aBmp;
    }
    else if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::PDF, aFlavor) &&
            TransferableDataHelper::IsEqual(aFlavor, rFlavor))
    {
        Graphic aGraphic;
        if (std::unique_ptr<SvStream> xStm = GetSotStorageStream(rFlavor))
        {
            if (GraphicConverter::Import(*xStm, aGraphic) == ERRCODE_NONE)
            {
                rGraphic = std::move(aGraphic);
                bRet = true;
            }
        }
    }
    else if (SotExchange::GetFormatDataFlavor(SotClipboardFormatId::JPEG, aFlavor) && TransferableDataHelper::IsEqual(aFlavor, rFlavor))
    {
        Bitmap aBitmap;

        bRet = GetBitmap(aFlavor, aBitmap);
        if (bRet)
            rGraphic = aBitmap;
    }
    else if(SotExchange::GetFormatDataFlavor( SotClipboardFormatId::BITMAP, aFlavor ) &&
        TransferableDataHelper::IsEqual( aFlavor, rFlavor ) )
    {
        Bitmap aBmp;

        bRet = GetBitmap(aFlavor, aBmp);
        if( bRet )
            rGraphic = aBmp;
    }
    else if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::GDIMETAFILE, aFlavor ) &&
             TransferableDataHelper::IsEqual( aFlavor, rFlavor ) )
    {
        GDIMetaFile aMtf;

        bRet = GetGDIMetaFile( aFlavor, aMtf );
        if( bRet )
            rGraphic = aMtf;
    }
    else
    {
        if (std::unique_ptr<SvStream> xStm = GetSotStorageStream( rFlavor) )
        {
            TypeSerializer aSerializer(*xStm);
            aSerializer.readGraphic(rGraphic);
            bRet = ( xStm->GetError() == ERRCODE_NONE );
        }
    }

    return bRet;
}


bool TransferableDataHelper::GetImageMap( SotClipboardFormatId nFormat, ImageMap& rIMap ) const
{
    DataFlavor aFlavor;
    return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetImageMap( aFlavor, rIMap ) );
}


bool TransferableDataHelper::GetImageMap( const css::datatransfer::DataFlavor& rFlavor, ImageMap& rIMap ) const
{
    std::unique_ptr<SvStream> xStm = GetSotStorageStream( rFlavor);
    if (!xStm)
        return false;

    rIMap.Read( *xStm );
    bool bRet = ( xStm->GetError() == ERRCODE_NONE );

    return bRet;
}


bool TransferableDataHelper::GetTransferableObjectDescriptor( SotClipboardFormatId nFormat, TransferableObjectDescriptor& rDesc ) const
{
    DataFlavor aFlavor;
    return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetTransferableObjectDescriptor( rDesc ) );
}


bool TransferableDataHelper::GetTransferableObjectDescriptor( TransferableObjectDescriptor& rDesc ) const
{
    rDesc = *mxObjDesc;
    return true;
}


bool TransferableDataHelper::GetINetBookmark( SotClipboardFormatId nFormat, INetBookmark& rBmk ) const
{
    DataFlavor aFlavor;
    return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetINetBookmark( aFlavor, rBmk ) );
}


bool TransferableDataHelper::GetINetBookmark( const css::datatransfer::DataFlavor& rFlavor, INetBookmark& rBmk ) const
{
    if( !HasFormat( rFlavor ))
        return false;

    bool bRet = false;
    const SotClipboardFormatId nFormat = SotExchange::GetFormat( rFlavor );
    switch( nFormat )
    {
        case SotClipboardFormatId::SOLK:
        case SotClipboardFormatId::UNIFORMRESOURCELOCATOR:
        {
            OUString aString;
            if( GetString( rFlavor, aString ) )
            {
                if( SotClipboardFormatId::UNIFORMRESOURCELOCATOR == nFormat )
                {
                    rBmk = INetBookmark( aString, aString );
                    bRet = true;
                }
                else
                {
                    OUString    aURL, aDesc;
                    sal_Int32   nStart = aString.indexOf( '@' ), nLen = aString.toInt32();

                    if( !nLen && aString[ 0 ] != '0' )
                    {
                        SAL_INFO( "svtools", "SOLK: 1. len=0" );
                    }
                    if( nStart == -1 || nLen > aString.getLength() - nStart - 3 )
                    {
                        SAL_INFO( "svtools", "SOLK: 1. illegal start or wrong len" );
                    }
                    aURL = aString.copy( nStart + 1, nLen );

                    aString = aString.replaceAt( 0, nStart + 1 + nLen, u"" );
                    nStart = aString.indexOf( '@' );
                    nLen = aString.toInt32();

                    if( !nLen && aString[ 0 ] != '0' )
                    {
                        SAL_INFO( "svtools", "SOLK: 2. len=0" );
                    }
                    if( nStart == -1 || nLen > aString.getLength() - nStart - 1 )
                    {
                        SAL_INFO( "svtools", "SOLK: 2. illegal start or wrong len" );
                    }
                    aDesc = aString.copy( nStart+1, nLen );

                    rBmk = INetBookmark( aURL, aDesc );
                    bRet = true;
                }
            }
        }
        break;

        case SotClipboardFormatId::NETSCAPE_BOOKMARK:
        {
            Sequence<sal_Int8> aSeq = GetSequence(rFlavor, OUString());

            if (2048 == aSeq.getLength())
            {
                const char* p1 = reinterpret_cast< const char* >( aSeq.getConstArray() );
                const char* p2 =  reinterpret_cast< const char* >( aSeq.getConstArray() ) + 1024;
                rBmk = INetBookmark( OUString( p1, strlen(p1), osl_getThreadTextEncoding() ),
                                     OUString( p2, strlen(p2), osl_getThreadTextEncoding() ) );
                bRet = true;
            }
        }
        break;

#ifdef _WIN32
        case SotClipboardFormatId::FILEGRPDESCRIPTOR:
        {
            Sequence<sal_Int8> aSeq = GetSequence(rFlavor, OUString());

            if (aSeq.getLength())
            {
                FILEGROUPDESCRIPTORW const * pFDesc = reinterpret_cast<FILEGROUPDESCRIPTORW const *>(aSeq.getConstArray());

                if( pFDesc->cItems )
                {
                    OUString aDesc( o3tl::toU(pFDesc->fgd[ 0 ].cFileName) );

                    if( ( aDesc.getLength() > 4 ) && aDesc.endsWithIgnoreAsciiCase(".URL") )
                    {
                        std::unique_ptr<SvStream> pStream(::utl::UcbStreamHelper::CreateStream( INetURLObject( aDesc ).GetMainURL( INetURLObject::DecodeMechanism::NONE ),
                                                                                  StreamMode::STD_READ ));

                        if( !pStream || pStream->GetError() )
                        {
                            DataFlavor aFileContentFlavor;

                            aSeq.realloc( 0 );
                            pStream.reset();

                            if (SotExchange::GetFormatDataFlavor(SotClipboardFormatId::FILECONTENT, aFileContentFlavor))
                            {
                                aSeq = GetSequence(aFileContentFlavor, OUString());
                                if (aSeq.getLength())
                                    pStream.reset(new SvMemoryStream( const_cast<sal_Int8 *>(aSeq.getConstArray()), aSeq.getLength(), StreamMode::STD_READ ));
                            }
                        }

                        if( pStream )
                        {
                            OString aLine;
                            bool bInA = false, bInW = false, bAFound = false;

                            while( pStream->ReadLine( aLine ) )
                            {
                                if (aLine.startsWithIgnoreAsciiCase("[InternetShortcut", &aLine))
                                {
                                    // May be [InternetShortcut], or [InternetShortcut.A], or
                                    // [InternetShortcut.W] (the latter has UTF-7-encoded URL)
                                    bInW = aLine.equalsIgnoreAsciiCase(".W]");
                                    bInA = !bAFound && !bInW
                                           && (aLine == "]" || aLine.equalsIgnoreAsciiCase(".A]"));
                                }
                                else if (aLine.startsWith("["))
                                {
                                    bInA = bInW = false;
                                }
                                else if ((bInA || bInW) && aLine.startsWithIgnoreAsciiCase("URL="))
                                {
                                    auto eTextEncoding = bInW ? RTL_TEXTENCODING_UTF7
                                                              : osl_getThreadTextEncoding();
                                    rBmk = INetBookmark( OStringToOUString(aLine.subView(4), eTextEncoding),
                                                         aDesc.copy(0, aDesc.getLength() - 4) );
                                    bRet = true;
                                    if (bInW)
                                        break;
                                    else
                                        bAFound = true; // Keep looking for "W"
                                }
                            }
                        }
                    }
                }
            }
        }
        break;
#endif
        default: break;
    }
    return bRet;
}


bool TransferableDataHelper::GetINetImage( SotClipboardFormatId nFormat,
                                                INetImage& rINtImg ) const
{
    DataFlavor aFlavor;
    return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetINetImage( aFlavor, rINtImg ) );
}


bool TransferableDataHelper::GetINetImage(
        const css::datatransfer::DataFlavor& rFlavor,
        INetImage& rINtImg ) const
{
    std::unique_ptr<SvStream> xStm = GetSotStorageStream( rFlavor );
    if (!xStm)
        return false;

    bool bRet = rINtImg.Read( *xStm, SotExchange::GetFormat( rFlavor ) );
    return bRet;
}


bool TransferableDataHelper::GetFileList( SotClipboardFormatId nFormat,
                                                FileList& rFileList ) const
{
    DataFlavor aFlavor;
    return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetFileList( rFileList ) );
}


bool TransferableDataHelper::GetFileList( FileList& rFileList ) const
{
    bool                bRet = false;

    for( sal_uInt32 i = 0, nFormatCount = GetFormatCount(); ( i < nFormatCount ) && !bRet; ++i )
    {
        if( SotClipboardFormatId::FILE_LIST == GetFormat( i ) )
        {
            const DataFlavor aFlavor( GetFormatDataFlavor( i ) );

            if (std::unique_ptr<SvStream> xStm = GetSotStorageStream( aFlavor ))
            {
                if( aFlavor.MimeType.indexOf( "text/uri-list" ) > -1 )
                {
                    OStringBuffer aDiskString;

                    while( xStm->ReadLine( aDiskString ) )
                        if( !aDiskString.isEmpty() && aDiskString[0] != '#' )
                            rFileList.AppendFile( OStringToOUString(aDiskString, RTL_TEXTENCODING_UTF8) );

                    bRet = true;
                }
                else
                    bRet = ( ReadFileList( *xStm, rFileList ).GetError() == ERRCODE_NONE );
            }
        }
    }

    return bRet;
}


Sequence<sal_Int8> TransferableDataHelper::GetSequence( SotClipboardFormatId nFormat, const OUString& rDestDoc ) const
{
    DataFlavor aFlavor;
    if (!SotExchange::GetFormatDataFlavor(nFormat, aFlavor))
        return Sequence<sal_Int8>();

    return GetSequence(aFlavor, rDestDoc);
}

Sequence<sal_Int8> TransferableDataHelper::GetSequence( const DataFlavor& rFlavor, const OUString& rDestDoc ) const
{
    const Any aAny = GetAny(rFlavor, rDestDoc);
    Sequence<sal_Int8> aSeq;
    if (aAny.hasValue())
        aAny >>= aSeq;

    return aSeq;
}

std::unique_ptr<SvStream> TransferableDataHelper::GetSotStorageStream( SotClipboardFormatId nFormat ) const
{
    DataFlavor aFlavor;
    if (!SotExchange::GetFormatDataFlavor( nFormat, aFlavor ))
        return nullptr;
    return GetSotStorageStream( aFlavor );
}

std::unique_ptr<SvStream> TransferableDataHelper::GetSotStorageStream( const DataFlavor& rFlavor ) const
{
    Sequence<sal_Int8> aSeq = GetSequence(rFlavor, OUString());
    if (!aSeq.hasElements())
        return nullptr;

    std::unique_ptr<SvStream> xStream = SotTempStream::Create( u""_ustr );
    xStream->WriteBytes( aSeq.getConstArray(), aSeq.getLength() );
    xStream->Seek(0);
    return xStream;
}

Reference<XInputStream> TransferableDataHelper::GetInputStream( SotClipboardFormatId nFormat, const OUString& rDestDoc ) const
{
    DataFlavor aFlavor;
    if (!SotExchange::GetFormatDataFlavor(nFormat, aFlavor))
        return Reference<XInputStream>();

    return GetInputStream(aFlavor, rDestDoc);
}

Reference<XInputStream> TransferableDataHelper::GetInputStream( const DataFlavor& rFlavor, const OUString& rDestDoc ) const
{
    Sequence<sal_Int8> aSeq = GetSequence(rFlavor, rDestDoc);

    if (!aSeq.hasElements())
        return Reference<XInputStream>();

    Reference<XInputStream> xStream(new comphelper::SequenceInputStream(aSeq));
    return xStream;
}

void TransferableDataHelper::Rebind( const Reference< XTransferable >& _rxNewContent )
{
    mxTransfer = _rxNewContent;
    InitFormats();
}

bool TransferableDataHelper::StartClipboardListening( )
{
    SolarMutexGuard g;

    StopClipboardListening( );

    mxClipboardListener = new TransferableClipboardNotifier(mxClipboard, *this);

    return mxClipboardListener->isListening();
}

void TransferableDataHelper::StopClipboardListening( )
{
    SolarMutexGuard g;

    if (mxClipboardListener.is())
    {
        mxClipboardListener->dispose();
        mxClipboardListener.clear();
    }
}

TransferableDataHelper TransferableDataHelper::CreateFromClipboard(const css::uno::Reference<css::datatransfer::clipboard::XClipboard>& rClipboard)
{
    TransferableDataHelper  aRet;

    if( rClipboard.is() )
    {
        try
        {
            Reference< XTransferable > xTransferable( rClipboard->getContents() );

            if( xTransferable.is() )
            {
                aRet = TransferableDataHelper( xTransferable );
                // also copy the clipboard
                aRet.mxClipboard = rClipboard;
            }
        }
        catch( const css::uno::Exception& )
        {
        }
    }

    return aRet;
}

TransferableDataHelper TransferableDataHelper::CreateFromSystemClipboard( vcl::Window * pWindow )
{
    DBG_ASSERT( pWindow, "Window pointer is NULL" );

    Reference< XClipboard > xClipboard;

    if( pWindow )
        xClipboard = pWindow->GetClipboard();

    return CreateFromClipboard(xClipboard);
}

TransferableDataHelper TransferableDataHelper::CreateFromPrimarySelection()
{
    Reference< XClipboard > xSelection(GetSystemPrimarySelection());
    TransferableDataHelper   aRet;

    if( xSelection.is() )
    {
           SolarMutexReleaser aReleaser;

           try
               {
                   Reference< XTransferable > xTransferable( xSelection->getContents() );

                   if( xTransferable.is() )
                   {
                           aRet = TransferableDataHelper( xTransferable );
                           aRet.mxClipboard = std::move(xSelection);
                   }
               }
           catch( const css::uno::Exception& )
               {
               }
    }

    return aRet;
}

bool TransferableDataHelper::IsEqual( const css::datatransfer::DataFlavor& rInternalFlavor,
                                      const css::datatransfer::DataFlavor& rRequestFlavor )
{
    const Reference< XComponentContext >&          xContext( ::comphelper::getProcessComponentContext() );
    bool                                    bRet = false;

    try
    {
        Reference< XMimeContentTypeFactory >    xMimeFact = MimeContentTypeFactory::create( xContext );

        Reference< XMimeContentType > xRequestType1( xMimeFact->createMimeContentType( rInternalFlavor.MimeType ) );
        Reference< XMimeContentType > xRequestType2( xMimeFact->createMimeContentType( rRequestFlavor.MimeType ) );

        if( xRequestType1.is() && xRequestType2.is() )
        {
            if( xRequestType1->getFullMediaType().equalsIgnoreAsciiCase( xRequestType2->getFullMediaType() ) )
            {
                if( xRequestType1->getFullMediaType().equalsIgnoreAsciiCase( "text/plain" ) )
                {
                    // special handling for text/plain media types
                    static constexpr OUString aCharsetString( u"charset"_ustr );

                    if( !xRequestType2->hasParameter( aCharsetString ) ||
                        xRequestType2->getParameterValue( aCharsetString ).equalsIgnoreAsciiCase( "utf-16" ) ||
                        xRequestType2->getParameterValue( aCharsetString ).equalsIgnoreAsciiCase( "unicode" ) )
                    {
                        bRet = true;
                    }
                }
                else if( xRequestType1->getFullMediaType().equalsIgnoreAsciiCase( "application/x-openoffice" ) )
                {
                    // special handling for application/x-openoffice media types
                    static constexpr OUString aFormatString( u"windows_formatname"_ustr );

                    if( xRequestType1->hasParameter( aFormatString ) &&
                        xRequestType2->hasParameter( aFormatString ) &&
                        xRequestType1->getParameterValue( aFormatString ).equalsIgnoreAsciiCase( xRequestType2->getParameterValue( aFormatString ) ) )
                    {
                        bRet = true;
                    }
                }
                else
                    bRet = true;
            }
        }
    }
    catch( const css::uno::Exception& )
    {
        bRet = rInternalFlavor.MimeType.equalsIgnoreAsciiCase( rRequestFlavor.MimeType );
    }

    return bRet;
}

static bool WriteDDELink_impl(SvStream& stream, rtl_TextEncoding encoding,
                              std::u16string_view application, std::u16string_view topic,
                              std::u16string_view item, std::u16string_view extra)
{
    // Put Link format, potentially with extra: either application\0topic\0item\0\0
    // or application\0topic\0item\0extra\0\0
    assert(rtl_isOctetTextEncoding(encoding)); // The Link format is 8-bit
    stream.WriteUnicodeOrByteText(application, encoding, true);
    stream.WriteUnicodeOrByteText(topic, encoding, true);
    stream.WriteUnicodeOrByteText(item, encoding, true);
    if (!extra.empty())
        stream.WriteUnicodeOrByteText(extra, encoding, true);
    stream.WriteChar('\0'); // One more trailing zero
    return stream.GetErrorCode() == ERRCODE_NONE;
}

// static
bool TransferableDataHelper::WriteDDELink(SvStream& stream, std::u16string_view application,
                                          std::u16string_view topic, std::u16string_view item,
                                          std::u16string_view extra)
{
    // 1. Put standard Link format in the system encoding
    WriteDDELink_impl(stream, osl_getThreadTextEncoding(), application, topic, item, extra);

    // 2. Put our extension to the format. Start with a magic string "ULnk", followed by
    // UTF-8-encoded Link format
    stream.WriteOString("ULnk");
    return WriteDDELink_impl(stream, RTL_TEXTENCODING_UTF8, application, topic, item, extra);
}

static size_t ReadDDELink_impl(std::string_view str, std::string_view& application,
                               std::string_view& topic, std::string_view& item,
                               std::string_view& rest)
{
    application = {};
    topic = {};
    item = {};
    rest = {};

    size_t start = 0;
    size_t end = str.find('\0', start);
    application = str.substr(start, end - start);
    if (end == std::string_view::npos)
        return end;
    start = end + 1;
    end = str.find('\0', start);
    topic = str.substr(start, end - start);
    if (end == std::string_view::npos)
        return end;
    start = end + 1;
    end = str.find('\0', start);
    item = str.substr(start, end - start);
    if (end >= str.size() - 1 || str[end + 1] == '\0')
        return end;
    start = end + 1;
    do
    {
        // Read all remaining data until \0\0 into rest, including possible single \0
        end = str.find('\0', end + 1);
    } while (end < str.size() - 1 && str[end + 1] != '\0');
    rest = str.substr(start, end - start);
    return end;
}

bool TransferableDataHelper::ReadDDELink(OUString& application, OUString& topic, OUString& item,
                                         OUString& rest) const
{
    css::uno::Sequence<sal_Int8> sequence = GetSequence(SotClipboardFormatId::LINK, {});
    if (!sequence.hasElements())
    {
        SAL_WARN("svtools", "DDE data not found");
        return false;
    }

    std::string_view str(reinterpret_cast<const char*>(sequence.getConstArray()),
                         sequence.getLength());
    std::string_view application_view, topic_view, item_view, rest_view;
    rtl_TextEncoding encoding = osl_getThreadTextEncoding();
    size_t end = ReadDDELink_impl(str, application_view, topic_view, item_view, rest_view);
    if (end < str.size() - 1 && str[end + 1] == '\0' && str.substr(end + 2, 4) == "ULnk")
    {
        // Now try to read our UTF-8 extension; if it's present, use it unconditionally, even if
        // osl_getThreadTextEncoding gives RTL_TEXTENCODING_UTF8, because the extension is immune
        // to possible different source encoding
        encoding = RTL_TEXTENCODING_UTF8;
        ReadDDELink_impl(str.substr(end + 6), application_view, topic_view, item_view, rest_view);
    }
    application = OStringToOUString(application_view, encoding);
    topic = OStringToOUString(topic_view, encoding);
    item = OStringToOUString(item_view, encoding);
    rest = OStringToOUString(rest_view, encoding);
    return !application.isEmpty() && !topic.isEmpty() && !item.isEmpty();
}

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