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

#include <utility>
#include <xeescher.hxx>

#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/form/FormComponentType.hpp>
#include <com/sun/star/awt/VisualEffect.hpp>
#include <com/sun/star/awt/ScrollBarOrientation.hpp>
#include <com/sun/star/awt/XControlModel.hpp>
#include <com/sun/star/drawing/XShape.hpp>
#include <com/sun/star/form/binding/XBindableValue.hpp>
#include <com/sun/star/form/binding/XListEntrySink.hpp>
#include <com/sun/star/awt/Size.hpp>
#include <com/sun/star/chart/XChartDocument.hpp>

#include <set>
#include <vcl/BitmapReadAccess.hxx>
#include <svx/svdoole2.hxx>
#include <svx/svdocapt.hxx>
#include <editeng/outlobj.hxx>
#include <unotools/tempfile.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <unotools/securityoptions.hxx>
#include <svtools/embedhlp.hxx>

#include <unonames.hxx>
#include <convuno.hxx>
#include <postit.hxx>

#include <fapihelper.hxx>
#include <xcl97esc.hxx>
#include <xechart.hxx>
#include <xeformula.hxx>
#include <xehelper.hxx>
#include <xelink.hxx>
#include <xename.hxx>
#include <xestyle.hxx>
#include <xllink.hxx>
#include <xltools.hxx>
#include <userdat.hxx>
#include <drwlayer.hxx>
#include <svl/itemset.hxx>
#include <svx/sdtaitm.hxx>
#include <document.hxx>
#include <svx/xfillit0.hxx>
#include <svx/xflclit.hxx>

#include <comphelper/sequence.hxx>
#include <oox/token/tokens.hxx>
#include <oox/token/relationship.hxx>
#include <oox/export/drawingml.hxx>
#include <oox/export/chartexport.hxx>
#include <oox/export/utils.hxx>
#include <oox/token/namespaces.hxx>
#include <oox/export/vmlexport.hxx>
#include <memory>

using namespace com::sun::star;
using ::com::sun::star::uno::UNO_QUERY;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::Sequence;
using ::com::sun::star::lang::XServiceInfo;
using ::com::sun::star::beans::XPropertySet;
using ::com::sun::star::drawing::XShape;
using ::com::sun::star::drawing::XShapes;
using ::com::sun::star::awt::XControlModel;
using ::com::sun::star::form::binding::XBindableValue;
using ::com::sun::star::form::binding::XListEntrySink;
using ::com::sun::star::script::ScriptEventDescriptor;
using ::com::sun::star::table::CellAddress;
using ::com::sun::star::table::CellRangeAddress;
using ::oox::drawingml::DrawingML;
using ::oox::drawingml::ChartExport;
using namespace oox;

namespace
{

const char *ToHorizAlign( SdrTextHorzAdjust eAdjust )
{
    switch( eAdjust )
    {
        case SDRTEXTHORZADJUST_CENTER:
            return "center";
        case SDRTEXTHORZADJUST_RIGHT:
            return "right";
        case SDRTEXTHORZADJUST_BLOCK:
            return "justify";
        case SDRTEXTHORZADJUST_LEFT:
        default:
            return "left";
    }
}

const char *ToVertAlign( SdrTextVertAdjust eAdjust )
{
    switch( eAdjust )
    {
        case SDRTEXTVERTADJUST_CENTER:
            return "center";
        case SDRTEXTVERTADJUST_BOTTOM:
            return "bottom";
        case SDRTEXTVERTADJUST_BLOCK:
            return "justify";
        case SDRTEXTVERTADJUST_TOP:
        default:
            return "top";
    }
}

void lcl_WriteAnchorVertex( sax_fastparser::FSHelperPtr const & rComments, const tools::Rectangle &aRect )
{
    rComments->startElement(FSNS(XML_xdr, XML_col));
    rComments->writeEscaped( OUString::number( aRect.Left() ) );
    rComments->endElement( FSNS( XML_xdr, XML_col ) );
    rComments->startElement(FSNS(XML_xdr, XML_colOff));
    rComments->writeEscaped( OUString::number( aRect.Top() ) );
    rComments->endElement( FSNS( XML_xdr, XML_colOff ) );
    rComments->startElement(FSNS(XML_xdr, XML_row));
    rComments->writeEscaped( OUString::number( aRect.Right() ) );
    rComments->endElement( FSNS( XML_xdr, XML_row ) );
    rComments->startElement(FSNS(XML_xdr, XML_rowOff));
    rComments->writeEscaped( OUString::number( aRect.Bottom() ) );
    rComments->endElement( FSNS( XML_xdr, XML_rowOff ) );
}

tools::Long lcl_hmm2output(tools::Long value, bool bInEMU)
{
    return o3tl::convert(value, o3tl::Length::mm100, bInEMU ? o3tl::Length::emu : o3tl::Length::px);
}

void lcl_GetFromTo( const XclExpRoot& rRoot, const tools::Rectangle &aRect, sal_Int32 nTab, tools::Rectangle &aFrom, tools::Rectangle &aTo, bool bInEMU = false )
{
    sal_Int32 nCol = 0, nRow = 0;
    sal_Int32 nColOff = 0, nRowOff= 0;

    const bool bRTL = rRoot.GetDoc().IsNegativePage( nTab );
    if (!bRTL)
    {
        while(true)
        {
            tools::Rectangle r = rRoot.GetDoc().GetMMRect( nCol,nRow,nCol,nRow,nTab );
            if( r.Left() <= aRect.Left() )
            {
                nCol++;
                nColOff = aRect.Left() - r.Left();
            }
            if( r.Top() <= aRect.Top() )
            {
                nRow++;
                nRowOff = aRect.Top() - r.Top();
            }
            if( r.Left() > aRect.Left() && r.Top() > aRect.Top() )
            {
                aFrom = tools::Rectangle( nCol-1, lcl_hmm2output( nColOff, bInEMU ),
                                   nRow-1, lcl_hmm2output( nRowOff, bInEMU ) );
                break;
            }
        }
    }
    else
    {
        while(true)
        {
            tools::Rectangle r = rRoot.GetDoc().GetMMRect( nCol,nRow,nCol,nRow,nTab );
            if( r.Left() >= aRect.Left() )
            {
                nCol++;
                nColOff = r.Left() - aRect.Left();
            }
            if( r.Top() <= aRect.Top() )
            {
                nRow++;
                nRowOff = aRect.Top() - r.Top();
            }
            if( r.Left() < aRect.Left() && r.Top() > aRect.Top() )
            {
                aFrom = tools::Rectangle( nCol-1, lcl_hmm2output( nColOff, bInEMU ),
                                   nRow-1, lcl_hmm2output( nRowOff, bInEMU ) );
                break;
            }
        }
    }
    if (!bRTL)
    {
        while(true)
        {
            tools::Rectangle r = rRoot.GetDoc().GetMMRect( nCol,nRow,nCol,nRow,nTab );
            if( r.Right() < aRect.Right() )
                nCol++;
            if( r.Bottom() < aRect.Bottom() )
                nRow++;
            if( r.Right() >= aRect.Right() && r.Bottom() >= aRect.Bottom() )
            {
                aTo = tools::Rectangle( nCol, lcl_hmm2output( aRect.Right() - r.Left(), bInEMU ),
                                 nRow, lcl_hmm2output( aRect.Bottom() - r.Top(), bInEMU ));
                break;
            }
        }
    }
    else
    {
        while(true)
        {
            tools::Rectangle r = rRoot.GetDoc().GetMMRect( nCol,nRow,nCol,nRow,nTab );
            if( r.Right() >= aRect.Right() )
                nCol++;
            if( r.Bottom() < aRect.Bottom() )
                nRow++;
            if( r.Right() < aRect.Right() && r.Bottom() >= aRect.Bottom() )
            {
                aTo = tools::Rectangle( nCol, lcl_hmm2output( r.Left() - aRect.Right(), bInEMU ),
                                 nRow, lcl_hmm2output( aRect.Bottom() - r.Top(), bInEMU ));
                break;
            }
        }
    }
}

} // namespace

// Escher client anchor =======================================================

XclExpDffAnchorBase::XclExpDffAnchorBase( const XclExpRoot& rRoot, sal_uInt16 nFlags ) :
    XclExpRoot( rRoot ),
    mnFlags( nFlags )
{
}

void XclExpDffAnchorBase::SetFlags( const SdrObject& rSdrObj )
{
    ImplSetFlags( rSdrObj );
}

void XclExpDffAnchorBase::SetSdrObject( const SdrObject& rSdrObj )
{
    ImplSetFlags( rSdrObj );
    ImplCalcAnchorRect( rSdrObj.GetCurrentBoundRect(), MapUnit::Map100thMM );
}

void XclExpDffAnchorBase::WriteDffData( EscherEx& rEscherEx ) const
{
    rEscherEx.AddAtom( 18, ESCHER_ClientAnchor );
    rEscherEx.GetStream().WriteUInt16( mnFlags );
    WriteXclObjAnchor( rEscherEx.GetStream(), maAnchor );
}

void XclExpDffAnchorBase::WriteData( EscherEx& rEscherEx, const tools::Rectangle& rRect )
{
    // the passed rectangle is in twips
    ImplCalcAnchorRect( rRect, MapUnit::MapTwip );
    WriteDffData( rEscherEx );
}

void XclExpDffAnchorBase::ImplSetFlags( const SdrObject& )
{
    OSL_FAIL( "XclExpDffAnchorBase::ImplSetFlags - not implemented" );
}

void XclExpDffAnchorBase::ImplCalcAnchorRect( const tools::Rectangle&, MapUnit )
{
    OSL_FAIL( "XclExpDffAnchorBase::ImplCalcAnchorRect - not implemented" );
}

XclExpDffSheetAnchor::XclExpDffSheetAnchor( const XclExpRoot& rRoot ) :
    XclExpDffAnchorBase( rRoot ),
    mnScTab( rRoot.GetCurrScTab() )
{
}

void XclExpDffSheetAnchor::ImplSetFlags( const SdrObject& rSdrObj )
{
    // set flags for cell/page anchoring
    if ( ScDrawLayer::GetAnchorType( rSdrObj ) == SCA_CELL )
        mnFlags = 0;
    else
        mnFlags = EXC_ESC_ANCHOR_LOCKED;
}

void XclExpDffSheetAnchor::ImplCalcAnchorRect( const tools::Rectangle& rRect, MapUnit eMapUnit )
{
    maAnchor.SetRect( GetRoot(), mnScTab, rRect, eMapUnit );
}

XclExpDffEmbeddedAnchor::XclExpDffEmbeddedAnchor( const XclExpRoot& rRoot,
        const Size& rPageSize, sal_Int32 nScaleX, sal_Int32 nScaleY ) :
    XclExpDffAnchorBase( rRoot ),
    maPageSize( rPageSize ),
    mnScaleX( nScaleX ),
    mnScaleY( nScaleY )
{
}

void XclExpDffEmbeddedAnchor::ImplSetFlags( const SdrObject& /*rSdrObj*/ )
{
    // TODO (unsupported feature): fixed size
}

void XclExpDffEmbeddedAnchor::ImplCalcAnchorRect( const tools::Rectangle& rRect, MapUnit eMapUnit )
{
    maAnchor.SetRect( maPageSize, mnScaleX, mnScaleY, rRect, eMapUnit );
}

XclExpDffNoteAnchor::XclExpDffNoteAnchor( const XclExpRoot& rRoot, const tools::Rectangle& rRect ) :
    XclExpDffAnchorBase( rRoot, EXC_ESC_ANCHOR_SIZELOCKED )
{
    maAnchor.SetRect( rRoot, rRoot.GetCurrScTab(), rRect, MapUnit::Map100thMM );
}

XclExpDffDropDownAnchor::XclExpDffDropDownAnchor( const XclExpRoot& rRoot, const ScAddress& rScPos ) :
    XclExpDffAnchorBase( rRoot, EXC_ESC_ANCHOR_POSLOCKED )
{
    GetAddressConverter().ConvertAddress( maAnchor.maFirst, rScPos, true );
    maAnchor.maLast.mnCol = maAnchor.maFirst.mnCol + 1;
    maAnchor.maLast.mnRow = maAnchor.maFirst.mnRow + 1;
    maAnchor.mnLX = maAnchor.mnTY = maAnchor.mnRX = maAnchor.mnBY = 0;
}

// MSODRAWING* records ========================================================

XclExpMsoDrawingBase::XclExpMsoDrawingBase( XclEscherEx& rEscherEx, sal_uInt16 nRecId ) :
    XclExpRecord( nRecId ),
    mrEscherEx( rEscherEx ),
    mnFragmentKey( rEscherEx.InitNextDffFragment() )
{
}

void XclExpMsoDrawingBase::WriteBody( XclExpStream& rStrm )
{
    OSL_ENSURE( mrEscherEx.GetStreamPos() == mrEscherEx.GetDffFragmentPos( mnFragmentKey ),
        "XclExpMsoDrawingBase::WriteBody - DFF stream position mismatch" );
    rStrm.CopyFromStream( mrEscherEx.GetStream(), mrEscherEx.GetDffFragmentSize( mnFragmentKey ) );
}

XclExpMsoDrawingGroup::XclExpMsoDrawingGroup( XclEscherEx& rEscherEx ) :
    XclExpMsoDrawingBase( rEscherEx, EXC_ID_MSODRAWINGGROUP )
{
    SvStream& rDffStrm = mrEscherEx.GetStream();

    // write the DGGCONTAINER with some default settings
    mrEscherEx.OpenContainer( ESCHER_DggContainer );

    // TODO: stuff the OPT atom with our own document defaults?
    static const sal_uInt8 spnDffOpt[] = {
        0xBF, 0x00, 0x08, 0x00, 0x08, 0x00, 0x81, 0x01,
        0x09, 0x00, 0x00, 0x08, 0xC0, 0x01, 0x40, 0x00,
        0x00, 0x08
    };
    mrEscherEx.AddAtom( sizeof( spnDffOpt ), ESCHER_OPT, 3, 3 );
    rDffStrm.WriteBytes(spnDffOpt, sizeof(spnDffOpt));

    // SPLITMENUCOLORS contains colors in toolbar
    static const sal_uInt8 spnDffSplitMenuColors[] = {
        0x0D, 0x00, 0x00, 0x08, 0x0C, 0x00, 0x00, 0x08,
        0x17, 0x00, 0x00, 0x08, 0xF7, 0x00, 0x00, 0x10
    };
    mrEscherEx.AddAtom( sizeof( spnDffSplitMenuColors ), ESCHER_SplitMenuColors, 0, 4 );
    rDffStrm.WriteBytes(spnDffSplitMenuColors, sizeof(spnDffSplitMenuColors));

    // close the DGGCONTAINER
    mrEscherEx.CloseContainer();
    mrEscherEx.UpdateDffFragmentEnd();
}

XclExpMsoDrawing::XclExpMsoDrawing( XclEscherEx& rEscherEx ) :
    XclExpMsoDrawingBase( rEscherEx, EXC_ID_MSODRAWING )
{
}

XclExpImgData::XclExpImgData( Graphic aGraphic, sal_uInt16 nRecId ) :
    maGraphic(std::move( aGraphic )),
    mnRecId( nRecId )
{
}

void XclExpImgData::Save( XclExpStream& rStrm )
{
    Bitmap aBmp = maGraphic.GetBitmap().CreateColorBitmap();
    if (aBmp.getPixelFormat() != vcl::PixelFormat::N24_BPP)
        aBmp.Convert( BmpConversion::N24Bit );

    BitmapScopedReadAccess pAccess(aBmp);
    if( !pAccess )
        return;

    sal_Int32 nWidth = ::std::min< sal_Int32 >( pAccess->Width(), 0xFFFF );
    sal_Int32 nHeight = ::std::min< sal_Int32 >( pAccess->Height(), 0xFFFF );
    if( (nWidth <= 0) || (nHeight <= 0) )
        return;

    sal_uInt8 nPadding = static_cast< sal_uInt8 >( nWidth & 0x03 );
    sal_uInt32 nTmpSize = static_cast< sal_uInt32 >( (nWidth * 3 + nPadding) * nHeight + 12 );

    rStrm.StartRecord( mnRecId, nTmpSize + 4 );

    rStrm   << EXC_IMGDATA_BMP                      // BMP format
            << EXC_IMGDATA_WIN                      // Windows
            << nTmpSize                             // size after _this_ field
            << sal_uInt32( 12 )                     // BITMAPCOREHEADER size
            << static_cast< sal_uInt16 >( nWidth )  // width
            << static_cast< sal_uInt16 >( nHeight ) // height
            << sal_uInt16( 1 )                      // planes
            << sal_uInt16( 24 );                    // bits per pixel

    for( sal_Int32 nY = nHeight - 1; nY >= 0; --nY )
    {
        Scanline pScanline = pAccess->GetScanline( nY );
        for( sal_Int32 nX = 0; nX < nWidth; ++nX )
        {
            const BitmapColor aBmpColor = pAccess->GetPixelFromData( pScanline, nX );
            rStrm << aBmpColor.GetBlue() << aBmpColor.GetGreen() << aBmpColor.GetRed();
        }
        rStrm.WriteZeroBytes( nPadding );
    }

    rStrm.EndRecord();
}

void XclExpImgData::SaveXml( XclExpXmlStream& rStrm )
{
    sax_fastparser::FSHelperPtr pWorksheet = rStrm.GetCurrentStream();

    DrawingML aDML(pWorksheet, &rStrm, drawingml::DOCUMENT_XLSX);
    OUString rId = aDML.writeGraphicToStorage(maGraphic);
    pWorksheet->singleElement(XML_picture, FSNS(XML_r, XML_id), rId);
}

XclExpControlHelper::XclExpControlHelper( const XclExpRoot& rRoot ) :
    XclExpRoot( rRoot ),
    mnEntryCount( 0 )
{
}

XclExpControlHelper::~XclExpControlHelper()
{
}

void XclExpControlHelper::ConvertSheetLinks( Reference< XShape > const & xShape )
{
    mxCellLink.reset();
    mxCellLinkAddress.SetInvalid();
    mxSrcRange.reset();
    mnEntryCount = 0;

    // get control model
    Reference< XControlModel > xCtrlModel = XclControlHelper::GetControlModel( xShape );
    if( !xCtrlModel.is() )
        return;

    // *** cell link *** ------------------------------------------------------

    Reference< XBindableValue > xBindable( xCtrlModel, UNO_QUERY );
    if( xBindable.is() )
    {
        Reference< XServiceInfo > xServInfo( xBindable->getValueBinding(), UNO_QUERY );
        if( xServInfo.is() && xServInfo->supportsService( SC_SERVICENAME_VALBIND ) )
        {
            ScfPropertySet aBindProp( xServInfo );
            CellAddress aApiAddress;
            if( aBindProp.GetProperty( aApiAddress, SC_UNONAME_BOUNDCELL ) )
            {
                ScUnoConversion::FillScAddress( mxCellLinkAddress, aApiAddress );
                if( GetTabInfo().IsExportTab( mxCellLinkAddress.Tab() ) )
                    mxCellLink = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_CONTROL, mxCellLinkAddress );
            }
        }
    }

    // *** source range *** ---------------------------------------------------

    Reference< XListEntrySink > xEntrySink( xCtrlModel, UNO_QUERY );
    if( !xEntrySink.is() )
        return;

    Reference< XServiceInfo > xServInfo( xEntrySink->getListEntrySource(), UNO_QUERY );
    if( !(xServInfo.is() && xServInfo->supportsService( SC_SERVICENAME_LISTSOURCE )) )
        return;

    ScfPropertySet aSinkProp( xServInfo );
    CellRangeAddress aApiRange;
    if( aSinkProp.GetProperty( aApiRange, SC_UNONAME_CELLRANGE ) )
    {
        ScRange aSrcRange;
        ScUnoConversion::FillScRange( aSrcRange, aApiRange );
        if( (aSrcRange.aStart.Tab() == aSrcRange.aEnd.Tab()) && GetTabInfo().IsExportTab( aSrcRange.aStart.Tab() ) )
            mxSrcRange = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_CONTROL, aSrcRange );
        mnEntryCount = static_cast< sal_uInt16 >( aSrcRange.aEnd.Col() - aSrcRange.aStart.Col() + 1 );
    }
}

void XclExpControlHelper::WriteFormula( XclExpStream& rStrm, const XclTokenArray& rTokArr )
{
    sal_uInt16 nFmlaSize = rTokArr.GetSize();
    rStrm << nFmlaSize << sal_uInt32( 0 );
    rTokArr.WriteArray( rStrm );
    if( nFmlaSize & 1 )             // pad to 16-bit
        rStrm << sal_uInt8( 0 );
}

void XclExpControlHelper::WriteFormulaSubRec( XclExpStream& rStrm, sal_uInt16 nSubRecId, const XclTokenArray& rTokArr )
{
    rStrm.StartRecord( nSubRecId, (rTokArr.GetSize() + 5) & ~1 );
    WriteFormula( rStrm, rTokArr );
    rStrm.EndRecord();
}

//delete for exporting OCX
//#if EXC_EXP_OCX_CTRL

XclExpOcxControlObj::XclExpOcxControlObj( XclExpObjectManager& rObjMgr, Reference< XShape > const & xShape,
        const tools::Rectangle* pChildAnchor, OUString aClassName, sal_uInt32 nStrmStart, sal_uInt32 nStrmSize ) :
    XclObj( rObjMgr, EXC_OBJTYPE_PICTURE, true ),
    XclExpControlHelper( rObjMgr.GetRoot() ),
    maClassName(std::move( aClassName )),
    mnStrmStart( nStrmStart ),
    mnStrmSize( nStrmSize )
{
    ScfPropertySet aCtrlProp( XclControlHelper::GetControlModel( xShape ) );

    // OBJ record flags
    SetLocked( true );
    SetPrintable( aCtrlProp.GetBoolProperty( u"Printable"_ustr ) );
    SetAutoFill( false );
    SetAutoLine( false );

    // fill DFF property set
    mrEscherEx.OpenContainer( ESCHER_SpContainer );
    mrEscherEx.AddShape( ESCHER_ShpInst_HostControl,
                         ShapeFlag::HaveShapeProperty | ShapeFlag::HaveAnchor | ShapeFlag::OLEShape );
    tools::Rectangle aDummyRect;
    EscherPropertyContainer aPropOpt( mrEscherEx.GetGraphicProvider(), mrEscherEx.QueryPictureStream(), aDummyRect );
    aPropOpt.AddOpt( ESCHER_Prop_FitTextToShape,    0x00080008 );   // bool field
    aPropOpt.AddOpt( ESCHER_Prop_lineColor,         0x08000040 );
    aPropOpt.AddOpt( ESCHER_Prop_fNoLineDrawDash,   0x00080000 );   // bool field

    // #i51348# name of the control, may overwrite shape name
    OUString aCtrlName;
    if( aCtrlProp.GetProperty( aCtrlName, u"Name"_ustr ) && !aCtrlName.isEmpty() )
        aPropOpt.AddOpt( ESCHER_Prop_wzName, aCtrlName );

    // meta file
    //TODO - needs check
    Reference< XPropertySet > xShapePS( xShape, UNO_QUERY );
    if( xShapePS.is() && aPropOpt.CreateGraphicProperties( xShapePS, u"MetaFile"_ustr, false ) )
    {
        sal_uInt32 nBlipId;
        if( aPropOpt.GetOpt( ESCHER_Prop_pib, nBlipId ) )
            aPropOpt.AddOpt( ESCHER_Prop_pictureId, nBlipId );
    }

    // write DFF property set to stream
    aPropOpt.Commit( mrEscherEx.GetStream() );

    // anchor
    ImplWriteAnchor( SdrObject::getSdrObjectFromXShape( xShape ), pChildAnchor );

    mrEscherEx.AddAtom( 0, ESCHER_ClientData );                       // OBJ record
    mrEscherEx.CloseContainer();  // ESCHER_SpContainer
    mrEscherEx.UpdateDffFragmentEnd();

    // spreadsheet links
    ConvertSheetLinks( xShape );
}

void XclExpOcxControlObj::WriteSubRecs( XclExpStream& rStrm )
{
    // OBJCF - clipboard format
    rStrm.StartRecord( EXC_ID_OBJCF, 2 );
    rStrm << sal_uInt16( 2 );
    rStrm.EndRecord();

    // OBJFLAGS
    rStrm.StartRecord( EXC_ID_OBJFLAGS, 2 );
    rStrm << sal_uInt16( 0x0031 );
    rStrm.EndRecord();

    // OBJPICTFMLA
    XclExpString aClass( maClassName );
    sal_uInt16 nClassNameSize = static_cast< sal_uInt16 >( aClass.GetSize() );
    sal_uInt16 nClassNamePad = nClassNameSize & 1;
    sal_uInt16 nFirstPartSize = 12 + nClassNameSize + nClassNamePad;

    const XclTokenArray* pCellLink = GetCellLinkTokArr();
    sal_uInt16 nCellLinkSize = pCellLink ? ((pCellLink->GetSize() + 7) & 0xFFFE) : 0;

    const XclTokenArray* pSrcRange = GetSourceRangeTokArr();
    sal_uInt16 nSrcRangeSize = pSrcRange ? ((pSrcRange->GetSize() + 7) & 0xFFFE) : 0;

    sal_uInt16 nPictFmlaSize = nFirstPartSize + nCellLinkSize + nSrcRangeSize + 18;
    rStrm.StartRecord( EXC_ID_OBJPICTFMLA, nPictFmlaSize );

    rStrm   << nFirstPartSize                           // size of first part
            << sal_uInt16( 5 )                          // formula size
            << sal_uInt32( 0 )                          // unknown ID
            << sal_uInt8( 0x02 ) << sal_uInt32( 0 )     // tTbl token with unknown ID
            << sal_uInt8( 3 )                           // pad to word
            << aClass;                                  // "Forms.***.1"
    rStrm.WriteZeroBytes( nClassNamePad );              // pad to word
    rStrm   << mnStrmStart                              // start in 'Ctls' stream
            << mnStrmSize                               // size in 'Ctls' stream
            << sal_uInt32( 0 );                         // class ID size
    // cell link
    rStrm << nCellLinkSize;
    if( pCellLink )
        WriteFormula( rStrm, *pCellLink );
    // list source range
    rStrm << nSrcRangeSize;
    if( pSrcRange )
        WriteFormula( rStrm, *pSrcRange );

    rStrm.EndRecord();
}

//#else

XclExpTbxControlObj::XclExpTbxControlObj( XclExpObjectManager& rRoot, Reference< XShape > const & xShape , const tools::Rectangle* pChildAnchor ) :
    XclObj( rRoot, EXC_OBJTYPE_UNKNOWN, true ),
    XclMacroHelper( rRoot ),
    mxShape( xShape ),
    meEventType( EXC_TBX_EVENT_ACTION ),
    mnHeight( 0 ),
    mnState( 0 ),
    mnLineCount( 0 ),
    mnSelEntry( 0 ),
    mnScrollValue( 0 ),
    mnScrollMin( 0 ),
    mnScrollMax( 100 ),
    mnScrollStep( 1 ),
    mnScrollPage( 10 ),
    mbFlatButton( false ),
    mbFlatBorder( false ),
    mbMultiSel( false ),
    mbScrollHor( false ),
    mbPrint( false ),
    mbVisible( false ),
    mnShapeId( 0 ),
    mrRoot(rRoot)
{
    namespace FormCompType = css::form::FormComponentType;
    namespace AwtVisualEffect = css::awt::VisualEffect;
    namespace AwtScrollOrient = css::awt::ScrollBarOrientation;

    ScfPropertySet aCtrlProp( XclControlHelper::GetControlModel( xShape ) );
    if( !xShape.is() || !aCtrlProp.Is() )
        return;

    mnHeight = xShape->getSize().Height;
    if( mnHeight <= 0 )
        return;

    // control type
    sal_Int16 nClassId = 0;
    if( aCtrlProp.GetProperty( nClassId, u"ClassId"_ustr ) )
    {
        switch( nClassId )
        {
            case FormCompType::COMMANDBUTTON:   mnObjType = EXC_OBJTYPE_BUTTON;       meEventType = EXC_TBX_EVENT_ACTION; break;
            case FormCompType::RADIOBUTTON:     mnObjType = EXC_OBJTYPE_OPTIONBUTTON; meEventType = EXC_TBX_EVENT_ACTION; break;
            case FormCompType::CHECKBOX:        mnObjType = EXC_OBJTYPE_CHECKBOX;     meEventType = EXC_TBX_EVENT_ACTION; break;
            case FormCompType::LISTBOX:         mnObjType = EXC_OBJTYPE_LISTBOX;      meEventType = EXC_TBX_EVENT_CHANGE; break;
            case FormCompType::COMBOBOX:        mnObjType = EXC_OBJTYPE_DROPDOWN;     meEventType = EXC_TBX_EVENT_CHANGE; break;
            case FormCompType::GROUPBOX:        mnObjType = EXC_OBJTYPE_GROUPBOX;     meEventType = EXC_TBX_EVENT_MOUSE;  break;
            case FormCompType::FIXEDTEXT:       mnObjType = EXC_OBJTYPE_LABEL;        meEventType = EXC_TBX_EVENT_MOUSE;  break;
            case FormCompType::SCROLLBAR:       mnObjType = EXC_OBJTYPE_SCROLLBAR;    meEventType = EXC_TBX_EVENT_VALUE;  break;
            case FormCompType::SPINBUTTON:      mnObjType = EXC_OBJTYPE_SPIN;         meEventType = EXC_TBX_EVENT_VALUE;  break;
        }
    }
    if( mnObjType == EXC_OBJTYPE_UNKNOWN )
        return;

    // OBJ record flags
    SetLocked( true );
    mbPrint = aCtrlProp.GetBoolProperty( u"Printable"_ustr );
    SetPrintable( mbPrint );
    SetAutoFill( false );
    SetAutoLine( false );

    // fill DFF property set
    mrEscherEx.OpenContainer( ESCHER_SpContainer );
    mrEscherEx.AddShape( ESCHER_ShpInst_HostControl, ShapeFlag::HaveAnchor | ShapeFlag::HaveShapeProperty );
    EscherPropertyContainer aPropOpt;
    mbVisible = aCtrlProp.GetBoolProperty( u"EnableVisible"_ustr );
    aPropOpt.AddOpt( ESCHER_Prop_fPrint, mbVisible ? 0x00080000 : 0x00080002 ); // visible flag

    aPropOpt.AddOpt( ESCHER_Prop_LockAgainstGrouping, 0x01000100 ); // bool field
    aPropOpt.AddOpt( ESCHER_Prop_lTxid, 0 );                        // Text ID
    aPropOpt.AddOpt( ESCHER_Prop_WrapText, 0x00000001 );
    aPropOpt.AddOpt( ESCHER_Prop_FitTextToShape, 0x001A0008 );      // bool field
    aPropOpt.AddOpt( ESCHER_Prop_fNoFillHitTest, 0x00100000 );      // bool field
    aPropOpt.AddOpt( ESCHER_Prop_fNoLineDrawDash, 0x00080000 );     // bool field

    // #i51348# name of the control, may overwrite shape name
    if( aCtrlProp.GetProperty( msCtrlName, u"Name"_ustr ) && !msCtrlName.isEmpty() )
        aPropOpt.AddOpt( ESCHER_Prop_wzName, msCtrlName );

    //Export description as alt text
    if( SdrObject* pSdrObj = SdrObject::getSdrObjectFromXShape( xShape ) )
    {
        OUString aAltTxt;
        OUString aDescrText = pSdrObj->GetDescription();
        if(!aDescrText.isEmpty())
            aAltTxt = aDescrText.copy( 0, std::min<sal_Int32>(MSPROP_DESCRIPTION_MAX_LEN, aDescrText.getLength()) );
        aPropOpt.AddOpt( ESCHER_Prop_wzDescription, aAltTxt );
    }

    ::Color aBackColor;
    if (aCtrlProp.GetProperty(aBackColor, u"BackgroundColor"_ustr))
        moBackgroundFill = aBackColor;

    // write DFF property set to stream
    aPropOpt.Commit( mrEscherEx.GetStream() );

    // anchor
    ImplWriteAnchor( SdrObject::getSdrObjectFromXShape( xShape ), pChildAnchor );

    mrEscherEx.AddAtom( 0, ESCHER_ClientData );                       // OBJ record
    mrEscherEx.UpdateDffFragmentEnd();

    // control label
    if( aCtrlProp.GetProperty( msLabel, u"Label"_ustr ) )
    {
        /*  Be sure to construct the MSODRAWING record containing the
            ClientTextbox atom after the base OBJ's MSODRAWING record data is
            completed. */
        pClientTextbox.reset( new XclExpMsoDrawing( mrEscherEx ) );
        mrEscherEx.AddAtom( 0, ESCHER_ClientTextbox );  // TXO record
        mrEscherEx.UpdateDffFragmentEnd();

        sal_uInt16 nXclFont = EXC_FONT_APP;
        if( !msLabel.isEmpty() )
        {
            XclFontData aFontData;
            GetFontPropSetHelper().ReadFontProperties( aFontData, aCtrlProp, EXC_FONTPROPSET_CONTROL );
            if( (!aFontData.maName.isEmpty() ) && (aFontData.mnHeight > 0) )
                nXclFont = GetFontBuffer().Insert( aFontData, EXC_COLOR_CTRLTEXT );
        }

        pTxo.reset( new XclTxo( msLabel, nXclFont ) );
        pTxo->SetHorAlign( (mnObjType == EXC_OBJTYPE_BUTTON) ? EXC_OBJ_HOR_CENTER : EXC_OBJ_HOR_LEFT );
        pTxo->SetVerAlign( EXC_OBJ_VER_CENTER );
    }

    mrEscherEx.CloseContainer();  // ESCHER_SpContainer

    // other properties
    aCtrlProp.GetProperty( mnLineCount, u"LineCount"_ustr );

    // border style
    sal_Int16 nApiButton = AwtVisualEffect::LOOK3D;
    sal_Int16 nApiBorder = AwtVisualEffect::LOOK3D;
    switch( nClassId )
    {
        case FormCompType::LISTBOX:
        case FormCompType::COMBOBOX:
            aCtrlProp.GetProperty( nApiBorder, u"Border"_ustr );
        break;
        case FormCompType::CHECKBOX:
        case FormCompType::RADIOBUTTON:
            aCtrlProp.GetProperty( nApiButton, u"VisualEffect"_ustr );
            nApiBorder = AwtVisualEffect::NONE;
        break;
        // Push button cannot be set to flat in Excel
        case FormCompType::COMMANDBUTTON:
        // Group box does not support flat style (#i34712#)
        case FormCompType::GROUPBOX:
            nApiBorder = AwtVisualEffect::LOOK3D;
        break;
        // Label does not support a border in Excel
        case FormCompType::FIXEDTEXT:
            nApiBorder = AwtVisualEffect::NONE;
        break;
        /*  Scroll bar and spin button have a "Border" property, but it is
            really used for a border, and not for own 3D/flat look (#i34712#). */
        case FormCompType::SCROLLBAR:
        case FormCompType::SPINBUTTON:
            nApiButton = AwtVisualEffect::LOOK3D;
            nApiBorder = AwtVisualEffect::NONE;
        break;
    }
    mbFlatButton = nApiButton != AwtVisualEffect::LOOK3D;
    mbFlatBorder = nApiBorder != AwtVisualEffect::LOOK3D;

    // control state
    sal_Int16 nApiState = 0;
    if( aCtrlProp.GetProperty( nApiState, u"State"_ustr ) )
    {
        switch( nApiState )
        {
            case 0: mnState = EXC_OBJ_CHECKBOX_UNCHECKED;  break;
            case 1: mnState = EXC_OBJ_CHECKBOX_CHECKED;    break;
            case 2: mnState = EXC_OBJ_CHECKBOX_TRISTATE;   break;
        }
    }

    // special control contents
    switch( nClassId )
    {
        case FormCompType::LISTBOX:
        {
            mbMultiSel = aCtrlProp.GetBoolProperty( u"MultiSelection"_ustr );
            Sequence< sal_Int16 > aSelection;
            if( aCtrlProp.GetProperty( aSelection, u"SelectedItems"_ustr ) )
            {
                if( aSelection.hasElements() )
                {
                    mnSelEntry = aSelection[ 0 ] + 1;
                    comphelper::sequenceToContainer(maMultiSel, aSelection);
                }
            }

            // convert listbox with dropdown button to Excel dropdown
            if( aCtrlProp.GetBoolProperty( u"Dropdown"_ustr ) )
                mnObjType = EXC_OBJTYPE_DROPDOWN;
        }
        break;

        case FormCompType::COMBOBOX:
        {
            Sequence< OUString > aStringList;
            OUString aDefText;
            if( aCtrlProp.GetProperty( aStringList, u"StringItemList"_ustr ) &&
                aCtrlProp.GetProperty( aDefText, u"Text"_ustr ) &&
                aStringList.hasElements() && !aDefText.isEmpty() )
            {
                auto nIndex = comphelper::findValue(aStringList, aDefText);
                if( nIndex != -1 )
                    mnSelEntry = static_cast< sal_Int16 >( nIndex + 1 );  // 1-based
                if( mnSelEntry > 0 )
                    maMultiSel.resize( 1, mnSelEntry - 1 );
            }

            // convert combobox without dropdown button to Excel listbox
            if( !aCtrlProp.GetBoolProperty( u"Dropdown"_ustr ) )
                mnObjType = EXC_OBJTYPE_LISTBOX;
        }
        break;

        case FormCompType::SCROLLBAR:
        {
            sal_Int32 nApiValue = 0;
            if( aCtrlProp.GetProperty( nApiValue, u"ScrollValueMin"_ustr ) )
                mnScrollMin = limit_cast< sal_uInt16 >( nApiValue, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX );
            if( aCtrlProp.GetProperty( nApiValue, u"ScrollValueMax"_ustr ) )
                mnScrollMax = limit_cast< sal_uInt16 >( nApiValue, mnScrollMin, EXC_OBJ_SCROLLBAR_MAX );
            if( aCtrlProp.GetProperty( nApiValue, u"ScrollValue"_ustr ) )
                mnScrollValue = limit_cast< sal_uInt16 >( nApiValue, mnScrollMin, mnScrollMax );
            if( aCtrlProp.GetProperty( nApiValue, u"LineIncrement"_ustr ) )
                mnScrollStep = limit_cast< sal_uInt16 >( nApiValue, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX );
            if( aCtrlProp.GetProperty( nApiValue, u"BlockIncrement"_ustr ) )
                mnScrollPage = limit_cast< sal_uInt16 >( nApiValue, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX );
            if( aCtrlProp.GetProperty( nApiValue, u"Orientation"_ustr ) )
                mbScrollHor = nApiValue == AwtScrollOrient::HORIZONTAL;
        }
        break;

        case FormCompType::SPINBUTTON:
        {
            sal_Int32 nApiValue = 0;
            if( aCtrlProp.GetProperty( nApiValue, u"SpinValueMin"_ustr ) )
                mnScrollMin = limit_cast< sal_uInt16 >( nApiValue, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX );
            if( aCtrlProp.GetProperty( nApiValue, u"SpinValueMax"_ustr ) )
                mnScrollMax = limit_cast< sal_uInt16 >( nApiValue, mnScrollMin, EXC_OBJ_SCROLLBAR_MAX );
            if( aCtrlProp.GetProperty( nApiValue, u"SpinValue"_ustr ) )
                mnScrollValue = limit_cast< sal_uInt16 >( nApiValue, mnScrollMin, mnScrollMax );
            if( aCtrlProp.GetProperty( nApiValue, u"SpinIncrement"_ustr ) )
                mnScrollStep = limit_cast< sal_uInt16 >( nApiValue, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX );
            if( aCtrlProp.GetProperty( nApiValue, u"Orientation"_ustr ) )
                mbScrollHor = nApiValue == AwtScrollOrient::HORIZONTAL;
        }
        break;
    }

    {
        Reference< XControlModel > xCtrlModel = XclControlHelper::GetControlModel( xShape );
        if( xCtrlModel.is() )
        {
            Reference< XBindableValue > xBindable( xCtrlModel, UNO_QUERY );
            if( xBindable.is() )
            {
                Reference< XServiceInfo > xServInfo( xBindable->getValueBinding(), UNO_QUERY );
                if( xServInfo.is() && xServInfo->supportsService( SC_SERVICENAME_VALBIND ) )
                {
                    ScfPropertySet aBindProp( xServInfo );
                    CellAddress aApiAddress;
                    if( aBindProp.GetProperty( aApiAddress, SC_UNONAME_BOUNDCELL ) )
                    {
                        ScUnoConversion::FillScAddress( mxCellLinkAddress, aApiAddress );
                        if( SdrObject* pSdrObj = SdrObject::getSdrObjectFromXShape( xShape ) )
                            lcl_GetFromTo( rRoot, pSdrObj->GetLogicRect(), mxCellLinkAddress.Tab(), maAreaFrom, maAreaTo, true );
                    }
                }
            }
        }
    }

    // spreadsheet links
    ConvertSheetLinks( xShape );
}

bool XclExpTbxControlObj::SetMacroLink( const ScriptEventDescriptor& rEvent )
{
    return XclMacroHelper::SetMacroLink( rEvent, meEventType );
}

void XclExpTbxControlObj::WriteSubRecs( XclExpStream& rStrm )
{
    switch( mnObjType )
    {
        // *** Push buttons, labels ***

        case EXC_OBJTYPE_BUTTON:
        case EXC_OBJTYPE_LABEL:
            // ftMacro - macro link
            WriteMacroSubRec( rStrm );
        break;

        // *** Check boxes, option buttons ***

        case EXC_OBJTYPE_CHECKBOX:
        case EXC_OBJTYPE_OPTIONBUTTON:
        {
            // ftCbls - box properties
            sal_uInt16 nStyle = 0;
            ::set_flag( nStyle, EXC_OBJ_CHECKBOX_FLAT, mbFlatButton );

            rStrm.StartRecord( EXC_ID_OBJCBLS, 12 );
            rStrm << mnState;
            rStrm.WriteZeroBytes( 8 );
            rStrm << nStyle;
            rStrm.EndRecord();

            // ftMacro - macro link
            WriteMacroSubRec( rStrm );
            // ftCblsFmla subrecord - cell link
            WriteCellLinkSubRec( rStrm, EXC_ID_OBJCBLSFMLA );

            // ftCblsData subrecord - box properties, again
            rStrm.StartRecord( EXC_ID_OBJCBLS, 8 );
            rStrm << mnState;
            rStrm.WriteZeroBytes( 4 );
            rStrm << nStyle;
            rStrm.EndRecord();
        }
        break;

        // *** List boxes, combo boxes ***

        case EXC_OBJTYPE_LISTBOX:
        case EXC_OBJTYPE_DROPDOWN:
        {
            sal_uInt16 nEntryCount = GetSourceEntryCount();

            // ftSbs subrecord - Scroll bars
            sal_Int32 nLineHeight = XclTools::GetHmmFromTwips( 200 );   // always 10pt
            if( mnObjType == EXC_OBJTYPE_LISTBOX )
                mnLineCount = static_cast< sal_uInt16 >( mnHeight / nLineHeight );
            mnScrollValue = 0;
            mnScrollMin = 0;
            sal_uInt16 nInvisLines = (nEntryCount >= mnLineCount) ? (nEntryCount - mnLineCount) : 0;
            mnScrollMax = limit_cast< sal_uInt16 >( nInvisLines, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX );
            mnScrollStep = 1;
            mnScrollPage = limit_cast< sal_uInt16 >( mnLineCount, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX );
            mbScrollHor = false;
            WriteSbs( rStrm );

            // ftMacro - macro link
            WriteMacroSubRec( rStrm );
            // ftSbsFmla subrecord - cell link
            WriteCellLinkSubRec( rStrm, EXC_ID_OBJSBSFMLA );

            // ftLbsData - source data range and box properties
            sal_uInt16 nStyle = 0;
            ::insert_value( nStyle, mbMultiSel ? EXC_OBJ_LISTBOX_MULTI : EXC_OBJ_LISTBOX_SINGLE, 4, 2 );
            ::set_flag( nStyle, EXC_OBJ_LISTBOX_FLAT, mbFlatBorder );

            rStrm.StartRecord( EXC_ID_OBJLBSDATA, 0 );

            if( const XclTokenArray* pSrcRange = GetSourceRangeTokArr() )
            {
                rStrm << static_cast< sal_uInt16 >( (pSrcRange->GetSize() + 7) & 0xFFFE );
                WriteFormula( rStrm, *pSrcRange );
            }
            else
                rStrm << sal_uInt16( 0 );

            rStrm << nEntryCount << mnSelEntry << nStyle << sal_uInt16( 0 );
            if( mnObjType == EXC_OBJTYPE_LISTBOX )
            {
                if( nEntryCount )
                {
                    ScfUInt8Vec aSelEx( nEntryCount, 0 );
                    for( const auto& rItem : maMultiSel )
                        if( rItem < nEntryCount )
                            aSelEx[ rItem ] = 1;
                    rStrm.Write( aSelEx.data(), aSelEx.size() );
                }
            }
            else if( mnObjType == EXC_OBJTYPE_DROPDOWN )
            {
                rStrm << sal_uInt16( 0 ) << mnLineCount << sal_uInt16( 0 ) << sal_uInt16( 0 );
            }

            rStrm.EndRecord();
        }
        break;

        // *** Spin buttons, scrollbars ***

        case EXC_OBJTYPE_SPIN:
        case EXC_OBJTYPE_SCROLLBAR:
        {
            // ftSbs subrecord - scroll bars
            WriteSbs( rStrm );
            // ftMacro - macro link
            WriteMacroSubRec( rStrm );
            // ftSbsFmla subrecord - cell link
            WriteCellLinkSubRec( rStrm, EXC_ID_OBJSBSFMLA );
        }
        break;

        // *** Group boxes ***

        case EXC_OBJTYPE_GROUPBOX:
        {
            // ftMacro - macro link
            WriteMacroSubRec( rStrm );

            // ftGboData subrecord - group box properties
            sal_uInt16 nStyle = 0;
            ::set_flag( nStyle, EXC_OBJ_GROUPBOX_FLAT, mbFlatBorder );

            rStrm.StartRecord( EXC_ID_OBJGBODATA, 6 );
            rStrm   << sal_uInt32( 0 )
                    << nStyle;
            rStrm.EndRecord();
        }
        break;
    }
}

void XclExpTbxControlObj::WriteCellLinkSubRec( XclExpStream& rStrm, sal_uInt16 nSubRecId )
{
    if( const XclTokenArray* pCellLink = GetCellLinkTokArr() )
        WriteFormulaSubRec( rStrm, nSubRecId, *pCellLink );
}

void XclExpTbxControlObj::WriteSbs( XclExpStream& rStrm )
{
    sal_uInt16 nOrient = 0;
    ::set_flag( nOrient, EXC_OBJ_SCROLLBAR_HOR, mbScrollHor );
    sal_uInt16 nStyle = EXC_OBJ_SCROLLBAR_DEFFLAGS;
    ::set_flag( nStyle, EXC_OBJ_SCROLLBAR_FLAT, mbFlatButton );

    rStrm.StartRecord( EXC_ID_OBJSBS, 20 );
    rStrm   << sal_uInt32( 0 )              // reserved
            << mnScrollValue                // thumb position
            << mnScrollMin                  // thumb min pos
            << mnScrollMax                  // thumb max pos
            << mnScrollStep                 // line increment
            << mnScrollPage                 // page increment
            << nOrient                      // 0 = vertical, 1 = horizontal
            << sal_uInt16( 15 )             // thumb width
            << nStyle;                      // flags/style
    rStrm.EndRecord();
}

void XclExpTbxControlObj::setShapeId(sal_Int32 aShapeId)
{
    mnShapeId = aShapeId;
}

namespace
{
/// Handles the VML export of form controls (e.g. checkboxes).
class VmlFormControlExporter : public oox::vml::VMLExport
{
    sal_uInt16 m_nObjType;
    tools::Rectangle m_aAreaFrom;
    tools::Rectangle m_aAreaTo;
    OUString m_sControlName;
    OUString m_sFmlaLink;
    OUString m_aLabel;
    OUString m_aMacroName;
    sal_Int16 m_nState;

public:
    VmlFormControlExporter(const sax_fastparser::FSHelperPtr& p, sal_uInt16 nObjType,
                           const tools::Rectangle& rAreaFrom, const tools::Rectangle& rAreaTo,
                           const OUString& sControlName, const OUString& sFmlaLink,
                           OUString aLabel, OUString aMacroName, sal_Int16 nState);
    bool m_bLook3d = true;
    std::optional<Color> m_oBackgroundFill;

protected:
    using VMLExport::StartShape;
    sal_Int32 StartShape() override;
    using VMLExport::EndShape;
    void EndShape(sal_Int32 nShapeElement) override;
};

VmlFormControlExporter::VmlFormControlExporter(const sax_fastparser::FSHelperPtr& p,
                                               sal_uInt16 nObjType,
                                               const tools::Rectangle& rAreaFrom,
                                               const tools::Rectangle& rAreaTo,
                                               const OUString& sControlName,
                                               const OUString& sFmlaLink,
                                               OUString aLabel, OUString aMacroName,
                                               sal_Int16 nState)
    : VMLExport(p)
    , m_nObjType(nObjType)
    , m_aAreaFrom(rAreaFrom)
    , m_aAreaTo(rAreaTo)
    , m_sControlName(sControlName)
    , m_sFmlaLink(sFmlaLink)
    , m_aLabel(std::move(aLabel))
    , m_aMacroName(std::move(aMacroName))
    , m_nState(nState)
{
}

sal_Int32 VmlFormControlExporter::StartShape()
{
    // Host control.
    AddShapeAttribute(XML_type, "#_x0000_t201");
    if (!m_sControlName.isEmpty())
        AddShapeAttribute(XML_id, m_sControlName.toUtf8());

    if (m_oBackgroundFill.has_value())
        AddShapeAttribute(XML_fillcolor,
                          OUString("#" + m_oBackgroundFill->AsRGBHexString()).toUtf8());
    else
        AddShapeAttribute(XML_filled, "f");

    return VMLExport::StartShape();
}

void VmlFormControlExporter::EndShape(sal_Int32 nShapeElement)
{
    sax_fastparser::FSHelperPtr pVmlDrawing = GetFS();

    pVmlDrawing->startElement(FSNS(XML_v, XML_textbox));
    pVmlDrawing->startElement(XML_div);
    pVmlDrawing->startElement(XML_font);
    pVmlDrawing->writeEscaped(m_aLabel);
    pVmlDrawing->endElement(XML_font);
    pVmlDrawing->endElement(XML_div);
    pVmlDrawing->endElement(FSNS(XML_v, XML_textbox));

    OString aObjectType;
    switch (m_nObjType)
    {
        case EXC_OBJTYPE_CHECKBOX:
            aObjectType = "Checkbox"_ostr;
            break;
        case EXC_OBJTYPE_BUTTON:
            aObjectType = "Button"_ostr;
            break;
    }
    pVmlDrawing->startElement(FSNS(XML_x, XML_ClientData), XML_ObjectType, aObjectType);
    OString aAnchor
        = OString::number(m_aAreaFrom.Left()) + ", " + OString::number(m_aAreaFrom.Top()) + ", "
          + OString::number(m_aAreaFrom.Right()) + ", " + OString::number(m_aAreaFrom.Bottom()) + ", "
          + OString::number(m_aAreaTo.Left()) + ", " + OString::number(m_aAreaTo.Top()) + ", "
          + OString::number(m_aAreaTo.Right()) + ", " + OString::number(m_aAreaTo.Bottom());
    XclXmlUtils::WriteElement(pVmlDrawing, FSNS(XML_x, XML_Anchor), aAnchor);

    if (!m_aMacroName.isEmpty())
    {
        XclXmlUtils::WriteElement(pVmlDrawing, FSNS(XML_x, XML_FmlaMacro), m_aMacroName);
    }

    if (m_nObjType == EXC_OBJTYPE_CHECKBOX && m_nState == EXC_OBJ_CHECKBOX_CHECKED)
    {
        XclXmlUtils::WriteElement(pVmlDrawing, FSNS(XML_x, XML_Checked), "1");
    }

    if (!m_bLook3d)
        pVmlDrawing->singleElement(FSNS(XML_x, XML_NoThreeD));

    // XclExpOcxControlObj::WriteSubRecs() has the same fixed values.
    if (m_nObjType == EXC_OBJTYPE_BUTTON)
    {
        XclXmlUtils::WriteElement(pVmlDrawing, FSNS(XML_x, XML_TextHAlign), "Center");
    }
    XclXmlUtils::WriteElement(pVmlDrawing, FSNS(XML_x, XML_TextVAlign), "Center");

    if (!m_sFmlaLink.isEmpty())
        XclXmlUtils::WriteElement(pVmlDrawing, FSNS(XML_x, XML_FmlaLink), m_sFmlaLink);

    pVmlDrawing->endElement(FSNS(XML_x, XML_ClientData));
    VMLExport::EndShape(nShapeElement);
}

}

/// Save into xl/drawings/vmlDrawing1.vml.
void XclExpTbxControlObj::SaveVml(XclExpXmlStream& rStrm)
{
    SdrObject* pObj = SdrObject::getSdrObjectFromXShape(mxShape);
    tools::Rectangle aAreaFrom;
    tools::Rectangle aAreaTo;
    // Unlike XclExpTbxControlObj::SaveXml(), this is not calculated in EMUs.
    lcl_GetFromTo(mrRoot, pObj->GetLogicRect(), GetTab(), aAreaFrom, aAreaTo);

    const OUString sCellLink
        = mxCellLinkAddress.IsValid()
              ? mxCellLinkAddress.Format(ScRefFlags::ADDR_ABS, &GetDoc(),
                                         ScAddress::Details(formula::FormulaGrammar::CONV_XL_A1))
              : OUString();

    VmlFormControlExporter aFormControlExporter(rStrm.GetCurrentStream(), GetObjType(), aAreaFrom,
                                                aAreaTo, msCtrlName, sCellLink, msLabel, GetMacroName(),
                                                mnState);
    aFormControlExporter.SetSkipwzName(true);  // use XML_id for legacyid, not XML_ID
    aFormControlExporter.OverrideShapeIDGen(true, "_x0000_s"_ostr);
    aFormControlExporter.m_bLook3d = !mbFlatButton;
    aFormControlExporter.m_oBackgroundFill = moBackgroundFill;
    aFormControlExporter.AddSdrObject(*pObj, /*bIsFollowingTextFlow=*/false, /*eHOri=*/-1,
                                      /*eVOri=*/-1, /*eHRel=*/-1, /*eVRel=*/-1,
                                      /*pWrapAttrList=*/nullptr, /*bOOxmlExport=*/true, mnShapeId);
}

void XclExpTbxControlObj::WriteAnchor(sax_fastparser::FSHelperPtr& rTarget, bool bIsDrawing) const
{
    tools::Rectangle aAreaFrom;
    tools::Rectangle aAreaTo;
    bool bNeedFromToCorrection = maAreaFrom.IsEmpty() || maAreaTo.IsEmpty();

    if (bNeedFromToCorrection)
    {
        SdrObject* pObj = SdrObject::getSdrObjectFromXShape(mxShape);
        lcl_GetFromTo(mrRoot, pObj->GetLogicRect(), GetTab(), aAreaFrom, aAreaTo, /*bInEMU=*/true);
    }

    const tools::Rectangle& rAreaFrom = bNeedFromToCorrection ? aAreaFrom : maAreaFrom;
    const tools::Rectangle& rAreaTo = bNeedFromToCorrection ? aAreaTo : maAreaTo;

    rTarget->startElement(bIsDrawing ? FSNS(XML_xdr, XML_from) : XML_from);
    lcl_WriteAnchorVertex(rTarget, rAreaFrom);
    rTarget->endElement(bIsDrawing ? FSNS(XML_xdr, XML_from) : XML_from);

    rTarget->startElement(bIsDrawing ? FSNS(XML_xdr, XML_to) : XML_to);
    lcl_WriteAnchorVertex(rTarget, rAreaTo);
    rTarget->endElement(bIsDrawing ? FSNS(XML_xdr, XML_to) : XML_to);
}

// save into xl\drawings\drawing1.xml
void XclExpTbxControlObj::SaveXml( XclExpXmlStream& rStrm )
{
    sax_fastparser::FSHelperPtr& pDrawing = rStrm.GetCurrentStream();

    pDrawing->startElement(FSNS(XML_mc, XML_AlternateContent),
        FSNS(XML_xmlns, XML_mc), rStrm.getNamespaceURL(OOX_NS(mce)));
    pDrawing->startElement(FSNS(XML_mc, XML_Choice),
        FSNS(XML_xmlns, XML_a14), rStrm.getNamespaceURL(OOX_NS(a14)),
        XML_Requires, "a14");

    pDrawing->startElement(FSNS(XML_xdr, XML_twoCellAnchor), XML_editAs, "oneCell");
    {
        WriteAnchor(pDrawing, /* bIsDrawing */ true);

        pDrawing->startElement(FSNS(XML_xdr, XML_sp));
        {
            // xdr:nvSpPr
            pDrawing->startElement(FSNS(XML_xdr, XML_nvSpPr));
            {
                pDrawing->singleElement(FSNS(XML_xdr, XML_cNvPr),
                    XML_id, OString::number(mnShapeId),
                    XML_name, msCtrlName, // control name
                    XML_descr, msLabel, // description as alt text
                    XML_hidden, mbVisible ? "0" : "1");
                pDrawing->singleElement(FSNS(XML_xdr, XML_cNvSpPr));
            }
            pDrawing->endElement(FSNS(XML_xdr, XML_nvSpPr));

            // xdr:spPr
            pDrawing->startElement(FSNS(XML_xdr, XML_spPr));
            {
                // a:xfrm
                pDrawing->startElement(FSNS(XML_a, XML_xfrm));
                {
                    pDrawing->singleElement(FSNS(XML_a, XML_off),
                        XML_x, "0",
                        XML_y, "0");
                    pDrawing->singleElement(FSNS(XML_a, XML_ext),
                        XML_cx, "0",
                        XML_cy, "0");
                }
                pDrawing->endElement(FSNS(XML_a, XML_xfrm));

                // a:prstGeom
                pDrawing->startElement(FSNS(XML_a, XML_prstGeom), XML_prst, "rect");
                {
                    pDrawing->singleElement(FSNS(XML_a, XML_avLst));
                }
                pDrawing->endElement(FSNS(XML_a, XML_prstGeom));
            }
            pDrawing->endElement(FSNS(XML_xdr, XML_spPr));

            // xdr:txBody
            {
                pDrawing->startElement(FSNS(XML_xdr, XML_txBody));

#define DEFLRINS 254
#define DEFTBINS 127
                sal_Int32 nLeft, nRight, nTop, nBottom;
                nLeft = nRight = DEFLRINS;
                nTop = nBottom = DEFTBINS;

                // top inset looks a bit different compared to ppt export
                // check if something related doesn't work as expected
                Reference< XPropertySet > rXPropSet(mxShape, UNO_QUERY);

                try
                {
                    css::uno::Any mAny;

                    mAny = rXPropSet->getPropertyValue(u"TextLeftDistance"_ustr);
                    if (mAny.hasValue())
                        mAny >>= nLeft;

                    mAny = rXPropSet->getPropertyValue(u"TextRightDistance"_ustr);
                    if (mAny.hasValue())
                        mAny >>= nRight;

                    mAny = rXPropSet->getPropertyValue(u"TextUpperDistance"_ustr);
                    if (mAny.hasValue())
                        mAny >>= nTop;

                    mAny = rXPropSet->getPropertyValue(u"TextLowerDistance"_ustr);
                    if (mAny.hasValue())
                        mAny >>= nBottom;
                }
                catch (...)
                {
                }

                // Specifies the inset of the bounding rectangle.
                // Insets are used just as internal margins for text boxes within shapes.
                // If this attribute is omitted, then a value of 45720 or 0.05 inches is implied.
                pDrawing->startElementNS(XML_a, XML_bodyPr,
                    XML_lIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeft)), nLeft != DEFLRINS),
                    XML_rIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nRight)), nRight != DEFLRINS),
                    XML_tIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nTop)), nTop != DEFTBINS),
                    XML_bIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nBottom)), nBottom != DEFTBINS),
                    XML_anchor, "ctr");

                {
                    bool bTextAutoGrowHeight = false;

                    try
                    {
                        css::uno::Any mAny;

                        mAny = rXPropSet->getPropertyValue(u"TextAutoGrowHeight"_ustr);
                        if (mAny.hasValue())
                            mAny >>= bTextAutoGrowHeight;
                    }
                    catch (...)
                    {
                    }

                    pDrawing->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit));
                }

                pDrawing->endElementNS(XML_a, XML_bodyPr);

                {
                    pDrawing->startElementNS(XML_a, XML_p);
                    pDrawing->startElementNS(XML_a, XML_r);
                    pDrawing->startElementNS(XML_a, XML_t);
                    pDrawing->writeEscaped(msLabel);
                    pDrawing->endElementNS(XML_a, XML_t);
                    pDrawing->endElementNS(XML_a, XML_r);
                    pDrawing->endElementNS(XML_a, XML_p);
                }

                pDrawing->endElement(FSNS(XML_xdr, XML_txBody));
            }
        }
        pDrawing->endElement(FSNS(XML_xdr, XML_sp));
        pDrawing->singleElement(FSNS(XML_xdr, XML_clientData));
    }
    pDrawing->endElement(FSNS(XML_xdr, XML_twoCellAnchor));
    pDrawing->endElement( FSNS( XML_mc, XML_Choice ) );
    pDrawing->endElement( FSNS( XML_mc, XML_AlternateContent ) );
}

// output into ctrlProp1.xml
OUString XclExpTbxControlObj::SaveControlPropertiesXml(XclExpXmlStream& rStrm) const
{
    OUString sIdFormControlPr;

    switch (mnObjType)
    {
        case EXC_OBJTYPE_CHECKBOX:
        {
            const sal_Int32 nDrawing = DrawingML::getNewDrawingUniqueId();
            sax_fastparser::FSHelperPtr pFormControl = rStrm.CreateOutputStream(
                    XclXmlUtils::GetStreamName( "xl/", "ctrlProps/ctrlProps", nDrawing ),
                    XclXmlUtils::GetStreamName( "../", "ctrlProps/ctrlProps", nDrawing ),
                    rStrm.GetCurrentStream()->getOutputStream(),
                    "application/vnd.ms-excel.controlproperties+xml",
                    oox::getRelationship(Relationship::CTRLPROP),
                    &sIdFormControlPr );

            rStrm.PushStream( pFormControl );
            // checkbox
            // <formControlPr
            //      xmlns="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"
            //      objectType="CheckBox" checked="Checked" lockText="1" noThreeD="1"/>
            //
            pFormControl->write("<formControlPr xmlns=\"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main\" objectType=\"CheckBox\"");
            if (mnState == EXC_OBJ_CHECKBOX_CHECKED)
                pFormControl->write(" checked=\"Checked\"");

            pFormControl->write(" autoLine=\"false\"");

            if (mbPrint)
                pFormControl->write(" print=\"true\"");
            else
                pFormControl->write(" print=\"false\"");

            if (mxCellLinkAddress.IsValid())
            {
                OUString aCellLink = mxCellLinkAddress.Format(ScRefFlags::ADDR_ABS,
                    &GetDoc(),
                    ScAddress::Details(::formula::FormulaGrammar::CONV_XL_A1));

                // "Sheet1!$C$5"
                pFormControl->write(" fmlaLink=\"");
                if (aCellLink.indexOf('!') < 0)
                {
                    pFormControl->write(GetTabInfo().GetScTabName(mxCellLinkAddress.Tab()));
                    pFormControl->write("!");
                }
                pFormControl->write(aCellLink);
                pFormControl->write("\"");
            }

            pFormControl->write(" lockText=\"1\" noThreeD=\"1\"/>");
            rStrm.PopStream();

            break;
        }
        case EXC_OBJTYPE_BUTTON:
        {
            sal_Int32 nDrawing = DrawingML::getNewDrawingUniqueId();
            sax_fastparser::FSHelperPtr pFormControl = rStrm.CreateOutputStream(
                XclXmlUtils::GetStreamName("xl/", "ctrlProps/ctrlProps", nDrawing),
                XclXmlUtils::GetStreamName("../", "ctrlProps/ctrlProps", nDrawing),
                rStrm.GetCurrentStream()->getOutputStream(),
                "application/vnd.ms-excel.controlproperties+xml",
                oox::getRelationship(Relationship::CTRLPROP), &sIdFormControlPr);

            pFormControl->singleElement(XML_formControlPr, XML_xmlns,
                                        rStrm.getNamespaceURL(OOX_NS(xls14Lst)), XML_objectType,
                                        "Button", XML_lockText, "1");
            break;
        }
    }

    return sIdFormControlPr;
}

// output into sheet1.xml
void XclExpTbxControlObj::SaveSheetXml(XclExpXmlStream& rStrm, const OUString& aIdFormControlPr) const
{
    switch (mnObjType)
    {
        case EXC_OBJTYPE_CHECKBOX:
        {
            sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();

            rWorksheet->startElement(FSNS(XML_mc, XML_AlternateContent),
                FSNS(XML_xmlns, XML_mc), rStrm.getNamespaceURL(OOX_NS(mce)));
            rWorksheet->startElement(FSNS(XML_mc, XML_Choice), XML_Requires, "x14");

            rWorksheet->startElement(
                XML_control,
                XML_shapeId, OString::number(mnShapeId),
                FSNS(XML_r, XML_id), aIdFormControlPr,
                XML_name, msLabel); // text to display with checkbox button

            rWorksheet->write("<controlPr defaultSize=\"0\" locked=\"1\" autoFill=\"0\" autoLine=\"0\" autoPict=\"0\"");

            if (mbPrint)
                rWorksheet->write(" print=\"true\"");
            else
                rWorksheet->write(" print=\"false\"");

            if (!msCtrlName.isEmpty())
            {
                rWorksheet->write(" altText=\"");
                rWorksheet->write(msCtrlName); // alt text
                rWorksheet->write("\"");
            }

            rWorksheet->write(">");

            rWorksheet->startElement(XML_anchor, XML_moveWithCells, "true", XML_sizeWithCells, "false");
            WriteAnchor(rWorksheet, /* bIsDrawing */ false);
            rWorksheet->endElement(XML_anchor);

            rWorksheet->write("</controlPr>");

            rWorksheet->endElement(XML_control);
            rWorksheet->endElement( FSNS( XML_mc, XML_Choice ) );
            rWorksheet->endElement( FSNS( XML_mc, XML_AlternateContent ) );

            break;
        }
        case EXC_OBJTYPE_BUTTON:
        {
            sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();

            rWorksheet->startElement(FSNS(XML_mc, XML_AlternateContent), FSNS(XML_xmlns, XML_mc),
                                     rStrm.getNamespaceURL(OOX_NS(mce)));
            rWorksheet->startElement(FSNS(XML_mc, XML_Choice), XML_Requires, "x14");

            rWorksheet->startElement(XML_control, XML_shapeId, OString::number(mnShapeId),
                                     FSNS(XML_r, XML_id), aIdFormControlPr, XML_name, msCtrlName);

            OUString aMacroName = GetMacroName();
            // Omit the macro attribute if it would be empty.
            rWorksheet->startElement(XML_controlPr, XML_defaultSize, "0", XML_print,
                                     mbPrint ? "true" : "false", XML_autoFill, "0", XML_autoPict,
                                     "0", XML_macro, sax_fastparser::UseIf(aMacroName, !aMacroName.isEmpty()));

            rWorksheet->startElement(XML_anchor, XML_moveWithCells, "true", XML_sizeWithCells,
                                     "false");

            SdrObject* pObj = SdrObject::getSdrObjectFromXShape(mxShape);
            tools::Rectangle aAreaFrom;
            tools::Rectangle aAreaTo;
            lcl_GetFromTo(mrRoot, pObj->GetLogicRect(), GetTab(), aAreaFrom, aAreaTo,
                          /*bInEMU=*/true);

            rWorksheet->startElement(XML_from);
            lcl_WriteAnchorVertex(rWorksheet, aAreaFrom);
            rWorksheet->endElement(XML_from);
            rWorksheet->startElement(XML_to);
            lcl_WriteAnchorVertex(rWorksheet, aAreaTo);
            rWorksheet->endElement(XML_to);
            rWorksheet->endElement(XML_anchor);

            rWorksheet->endElement(XML_controlPr);

            rWorksheet->endElement(XML_control);
            rWorksheet->endElement(FSNS(XML_mc, XML_Choice));
            rWorksheet->endElement(FSNS(XML_mc, XML_AlternateContent));
            break;
        }
    }
}

//#endif

XclExpChartObj::XclExpChartObj( XclExpObjectManager& rObjMgr, Reference< XShape > const & xShape, const tools::Rectangle* pChildAnchor, ScDocument* pDoc ) :
    XclObj( rObjMgr, EXC_OBJTYPE_CHART ),
    XclExpRoot( rObjMgr.GetRoot() ), mxShape( xShape ),
    mpDoc(pDoc)
{
    // create the MSODRAWING record contents for the chart object
    mrEscherEx.OpenContainer( ESCHER_SpContainer );
    mrEscherEx.AddShape( ESCHER_ShpInst_HostControl, ShapeFlag::HaveAnchor | ShapeFlag::HaveShapeProperty );
    EscherPropertyContainer aPropOpt;
    aPropOpt.AddOpt( ESCHER_Prop_LockAgainstGrouping, 0x01040104 );
    aPropOpt.AddOpt( ESCHER_Prop_FitTextToShape, 0x00080008 );
    aPropOpt.AddOpt( ESCHER_Prop_fillColor, 0x0800004E );
    aPropOpt.AddOpt( ESCHER_Prop_fillBackColor, 0x0800004D );
    aPropOpt.AddOpt( ESCHER_Prop_fNoFillHitTest, 0x00110010 );
    aPropOpt.AddOpt( ESCHER_Prop_lineColor, 0x0800004D );
    aPropOpt.AddOpt( ESCHER_Prop_fNoLineDrawDash, 0x00080008 );
    aPropOpt.AddOpt( ESCHER_Prop_fshadowObscured, 0x00020000 );
    aPropOpt.AddOpt( ESCHER_Prop_fPrint, 0x00080000 );
    aPropOpt.Commit( mrEscherEx.GetStream() );

    // anchor
    SdrObject* pSdrObj = SdrObject::getSdrObjectFromXShape( xShape );
    ImplWriteAnchor( pSdrObj, pChildAnchor );

    // client data (the following OBJ record)
    mrEscherEx.AddAtom( 0, ESCHER_ClientData );
    mrEscherEx.CloseContainer();  // ESCHER_SpContainer
    mrEscherEx.UpdateDffFragmentEnd();

    // load the chart OLE object
    if( SdrOle2Obj* pSdrOleObj = dynamic_cast< SdrOle2Obj* >( pSdrObj ) )
        (void)svt::EmbeddedObjectRef::TryRunningState(pSdrOleObj->GetObjRef());

    // create the chart substream object
    ScfPropertySet aShapeProp( xShape );
    css::awt::Rectangle aBoundRect;
    aShapeProp.GetProperty( aBoundRect, u"BoundRect"_ustr );
    tools::Rectangle aChartRect( Point( aBoundRect.X, aBoundRect.Y ), Size( aBoundRect.Width, aBoundRect.Height ) );
    mxChart = std::make_shared<XclExpChart>(GetRoot(), GetChartDoc(), aChartRect);
}

XclExpChartObj::~XclExpChartObj()
{
}

void XclExpChartObj::Save( XclExpStream& rStrm )
{
    // content of OBJ record
    XclObj::Save( rStrm );
    // chart substream
    mxChart->Save( rStrm );
}

void XclExpChartObj::SaveXml( XclExpXmlStream& rStrm )
{
    sax_fastparser::FSHelperPtr pDrawing = rStrm.GetCurrentStream();

    // FIXME: two cell? it seems the two cell anchor is incorrect.
    pDrawing->startElement( FSNS( XML_xdr, XML_twoCellAnchor ), // OOXTODO: oneCellAnchor, absoluteAnchor
            XML_editAs, "oneCell" );
    Reference< XPropertySet > xPropSet( mxShape, UNO_QUERY );
    if (xPropSet.is())
    {
        XclObjAny::WriteFromTo( rStrm, mxShape, GetTab() );
        ChartExport aChartExport(XML_xdr, pDrawing, GetChartDoc(), &rStrm, drawingml::DOCUMENT_XLSX);
        auto pURLTransformer = std::make_shared<ScURLTransformer>(*mpDoc);
        aChartExport.SetURLTranslator(pURLTransformer);
        sal_Int32 nChartCount = oox::drawingml::DrawingML::getNewChartUniqueId();
        sal_Int32 nID = rStrm.GetUniqueId();
        aChartExport.WriteChartObj( mxShape, nID, nChartCount );
        // TODO: get the correcto chart number
    }

    pDrawing->singleElement( FSNS( XML_xdr, XML_clientData)
            // OOXTODO: XML_fLocksWithSheet
            // OOXTODO: XML_fPrintsWithSheet
    );
    pDrawing->endElement( FSNS( XML_xdr, XML_twoCellAnchor ) );
}

css::uno::Reference<css::chart::XChartDocument> XclExpChartObj::GetChartDoc() const
{
    SdrObject* pObj = SdrObject::getSdrObjectFromXShape(mxShape);
    if (!pObj || pObj->GetObjIdentifier() != SdrObjKind::OLE2)
        return {};
    // May load here - makes sure that we are working with actually loaded OLE object
    return css::uno::Reference<css::chart::XChartDocument>(
        static_cast<SdrOle2Obj*>(pObj)->getXModel(), css::uno::UNO_QUERY);
}

XclExpNote::XclExpNote(const XclExpRoot& rRoot, const ScAddress& rScPos,
        const ScPostIt* pScNote, std::u16string_view rAddText)
    : XclExpRecord(EXC_ID_NOTE)
    , mrRoot(rRoot)
    , maScPos(rScPos)
    , mnObjId(EXC_OBJ_INVALID_ID)
    , mbVisible(pScNote && pScNote->IsCaptionShown())
    , meTHA(SDRTEXTHORZADJUST_LEFT)
    , meTVA(SDRTEXTVERTADJUST_TOP)
    , mbAutoScale(false)
    , mbLocked(false)
    , mbAutoFill(false)
    , mbColHidden(false)
    , mbRowHidden(false)
    , mpAuthorIDs(new SvtSecurityMapPersonalInfo)
{
    // get the main note text
    OUString aNoteText;
    if( pScNote )
        aNoteText = pScNote->GetText();

    // append additional text
    aNoteText = ScGlobal::addToken( aNoteText, rAddText, '\n', 2 );

    // initialize record dependent on BIFF type
    switch( rRoot.GetBiff() )
    {
        case EXC_BIFF5:
            maNoteText = OUStringToOString(aNoteText, rRoot.GetTextEncoding());
        break;

        case EXC_BIFF8:
        {
            // TODO: additional text
            if( pScNote )
            {
                if( SdrCaptionObj* pCaption = pScNote->GetOrCreateCaption( maScPos ) )
                {
                    lcl_GetFromTo( rRoot, pCaption->GetLogicRect(), maScPos.Tab(), maCommentFrom, maCommentTo );
                    if( const OutlinerParaObject* pOPO = pCaption->GetOutlinerParaObject() )
                        mnObjId = rRoot.GetObjectManager().AddObj( std::make_unique<XclObjComment>( rRoot.GetObjectManager(), pCaption->GetLogicRect(), pOPO->GetTextObject(), pCaption, mbVisible, maScPos, maCommentFrom, maCommentTo ) );

                    SfxItemSet aItemSet = pCaption->GetMergedItemSet();
                    meTVA       = pCaption->GetTextVerticalAdjust();
                    meTHA       = pCaption->GetTextHorizontalAdjust();
                    mbAutoScale = pCaption->GetFitToSize() != drawing::TextFitToSizeType_NONE;
                    mbLocked    = pCaption->IsMoveProtect() || pCaption->IsResizeProtect();

                    // AutoFill style would change if Postit.cxx object creation values are changed
                    OUString aCol(aItemSet.Get(XATTR_FILLCOLOR).GetValue());
                    mbAutoFill  = aCol.isEmpty() && (aItemSet.Get(XATTR_FILLSTYLE).GetValue() == drawing::FillStyle_SOLID);
                    mbRowHidden = (rRoot.GetDoc().RowHidden(maScPos.Row(),maScPos.Tab()));
                    mbColHidden = (rRoot.GetDoc().ColHidden(maScPos.Col(),maScPos.Tab()));
                }
                // stAuthor (variable): An XLUnicodeString that specifies the name of the comment
                // author. String length MUST be greater than or equal to 1 and less than or equal
                // to 54.
                bool bRemovePersonalInfo
                    = SvtSecurityOptions::IsOptionSet(
                          SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo)
                      && !SvtSecurityOptions::IsOptionSet(
                             SvtSecurityOptions::EOption::DocWarnKeepNoteAuthorDateInfo);
                if( pScNote->GetAuthor().isEmpty() )
                    maAuthor = XclExpString( u" "_ustr );
                else if (bRemovePersonalInfo)
                    maAuthor = XclExpString(
                        "Author"
                            + OUString::number(mpAuthorIDs->GetInfoID(pScNote->GetAuthor())),
                        XclStrFlags::NONE, 54);
                else
                    maAuthor = XclExpString( pScNote->GetAuthor(), XclStrFlags::NONE, 54 );

                if (const EditTextObject *pEditObj = pScNote->GetEditTextObject())
                    mpNoteContents = XclExpStringHelper::CreateString( rRoot, *pEditObj );
            }

            SetRecSize( 9 + maAuthor.GetSize() );
        }
        break;

        default:    DBG_ERROR_BIFF();
    }
}

void XclExpNote::Save( XclExpStream& rStrm )
{
    switch( rStrm.GetRoot().GetBiff() )
    {
        case EXC_BIFF5:
        {
            // write the NOTE record directly, there may be the need to create more than one
            const char* pcBuffer = maNoteText.getStr();
            sal_uInt16 nCharsLeft = static_cast< sal_uInt16 >( maNoteText.getLength() );

            while( nCharsLeft )
            {
                sal_uInt16 nWriteChars = ::std::min( nCharsLeft, EXC_NOTE5_MAXLEN );

                rStrm.StartRecord( EXC_ID_NOTE, 6 + nWriteChars );
                if( pcBuffer == maNoteText.getStr() )
                {
                    // first record: row, col, length of complete text
                    rStrm   << static_cast< sal_uInt16 >( maScPos.Row() )
                            << static_cast< sal_uInt16 >( maScPos.Col() )
                            << nCharsLeft;  // still contains full length
                }
                else
                {
                    // next records: -1, 0, length of current text segment
                    rStrm   << sal_uInt16( 0xFFFF )
                            << sal_uInt16( 0 )
                            << nWriteChars;
                }
                rStrm.Write( pcBuffer, nWriteChars );
                rStrm.EndRecord();

                pcBuffer += nWriteChars;
                nCharsLeft = nCharsLeft - nWriteChars;
            }
        }
        break;

        case EXC_BIFF8:
            if( mnObjId != EXC_OBJ_INVALID_ID )
                XclExpRecord::Save( rStrm );
        break;

        default:    DBG_ERROR_BIFF();
    }
}

void XclExpNote::WriteBody( XclExpStream& rStrm )
{
    // BIFF5/BIFF7 is written separately
    OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() == EXC_BIFF8 );

    sal_uInt16 nFlags = 0;
    ::set_flag( nFlags, EXC_NOTE_VISIBLE, mbVisible );

    rStrm   << static_cast< sal_uInt16 >( maScPos.Row() )
            << static_cast< sal_uInt16 >( maScPos.Col() )
            << nFlags
            << mnObjId
            << maAuthor
            << sal_uInt8( 0 );
}

void XclExpNote::WriteXml( sal_Int32 nAuthorId, XclExpXmlStream& rStrm )
{
    sax_fastparser::FSHelperPtr rComments = rStrm.GetCurrentStream();

    rComments->startElement( XML_comment,
            XML_ref,        XclXmlUtils::ToOString(mrRoot.GetDoc(), ScRange(maScPos)),
            XML_authorId,   OString::number(nAuthorId)
            // OOXTODO: XML_guid
    );
    rComments->startElement(XML_text);
    // OOXTODO: phoneticPr, rPh, r
    if( mpNoteContents )
        mpNoteContents->WriteXml( rStrm );
    rComments->endElement( XML_text );

/*
   Export of commentPr is disabled, since the current (Oct 2010)
   version of MSO 2010 doesn't yet support commentPr
 */
#if 1//def XLSX_OOXML_FUTURE
    if( rStrm.getVersion() == oox::core::ISOIEC_29500_2008 )
    {
        rComments->startElement(FSNS(XML_mc, XML_AlternateContent));
        rComments->startElement(FSNS(XML_mc, XML_Choice), XML_Requires, "v2");
        rComments->startElement( XML_commentPr,
                XML_autoFill,       ToPsz( mbAutoFill ),
                XML_autoScale,      ToPsz( mbAutoScale ),
                XML_colHidden,      ToPsz( mbColHidden ),
                XML_locked,         ToPsz( mbLocked ),
                XML_rowHidden,      ToPsz( mbRowHidden ),
                XML_textHAlign,     ToHorizAlign( meTHA ),
                XML_textVAlign,     ToVertAlign( meTVA )  );
        rComments->startElement(XML_anchor, XML_moveWithCells, "false", XML_sizeWithCells, "false");
        rComments->startElement(FSNS(XML_xdr, XML_from));
        lcl_WriteAnchorVertex( rComments, maCommentFrom );
        rComments->endElement( FSNS( XML_xdr, XML_from ) );
        rComments->startElement(FSNS(XML_xdr, XML_to));
        lcl_WriteAnchorVertex( rComments, maCommentTo );
        rComments->endElement( FSNS( XML_xdr, XML_to ) );
        rComments->endElement( XML_anchor );
        rComments->endElement( XML_commentPr );

        rComments->endElement( FSNS( XML_mc, XML_Choice ) );
        rComments->startElement(FSNS(XML_mc, XML_Fallback));
        // Any fallback code ?
        rComments->endElement( FSNS( XML_mc, XML_Fallback ) );
        rComments->endElement( FSNS( XML_mc, XML_AlternateContent ) );
    }
#endif
    rComments->endElement( XML_comment );
}

XclMacroHelper::XclMacroHelper( const XclExpRoot& rRoot ) :
    XclExpControlHelper( rRoot )
{
}

XclMacroHelper::~XclMacroHelper()
{
}

void XclMacroHelper::WriteMacroSubRec( XclExpStream& rStrm )
{
    if( mxMacroLink )
        WriteFormulaSubRec( rStrm, EXC_ID_OBJMACRO, *mxMacroLink );
}

const OUString& XclMacroHelper::GetMacroName() const { return maMacroName; }

bool
XclMacroHelper::SetMacroLink( const ScriptEventDescriptor& rEvent, const XclTbxEventType& nEventType )
{
    maMacroName = XclControlHelper::ExtractFromMacroDescriptor(rEvent, nEventType);
    if (!maMacroName.isEmpty())
    {
        return SetMacroLink(maMacroName);
    }
    return false;
}

bool
XclMacroHelper::SetMacroLink( const OUString& rMacroName )
{
    // OOXML documents do not store any defined name for VBA macros (while BIFF documents do).
    bool bOOXML = GetOutput() == EXC_OUTPUT_XML_2007;
    if (!rMacroName.isEmpty() && !bOOXML)
    {
        sal_uInt16 nExtSheet = GetLocalLinkManager().FindExtSheet( EXC_EXTSH_OWNDOC );
        sal_uInt16 nNameIdx
            = GetNameManager().InsertMacroCall(rMacroName, /*bVBasic=*/true, /*bFunc=*/false);
        mxMacroLink = GetFormulaCompiler().CreateNameXFormula( nExtSheet, nNameIdx );
        return true;
    }
    return false;
}

XclExpShapeObj::XclExpShapeObj( XclExpObjectManager& rRoot, css::uno::Reference< css::drawing::XShape > const & xShape, ScDocument* pDoc ) :
    XclObjAny( rRoot, xShape, pDoc ),
    XclMacroHelper( rRoot )
{
    if (SdrObject* pSdrObj = SdrObject::getSdrObjectFromXShape(xShape))
    {
        ScMacroInfo* pInfo = ScDrawLayer::GetMacroInfo( pSdrObj );
        if ( pInfo && !pInfo->GetMacro().isEmpty() )
// FIXME ooo330-m2: XclControlHelper::GetXclMacroName was removed in upstream sources; they started to call XclTools::GetXclMacroName instead; is this enough? it has only one parameter
//            SetMacroLink( XclControlHelper::GetXclMacroName( pInfo->GetMacro(), rRoot.GetDocShell() ) );
            SetMacroLink( XclTools::GetXclMacroName( pInfo->GetMacro() ) );
    }
}

XclExpShapeObj::~XclExpShapeObj()
{
}

void XclExpShapeObj::WriteSubRecs( XclExpStream& rStrm )
{
    XclObjAny::WriteSubRecs( rStrm );
    WriteMacroSubRec( rStrm );
}

XclExpComments::XclExpComments( SCTAB nTab, XclExpRecordList< XclExpNote >& rNotes )
    : mnTab( nTab ), mrNotes( rNotes )
{
}

void XclExpComments::SaveXml( XclExpXmlStream& rStrm )
{
    if( mrNotes.IsEmpty() )
        return;

    sax_fastparser::FSHelperPtr rComments = rStrm.CreateOutputStream(
            XclXmlUtils::GetStreamName( "xl/", "comments", mnTab + 1 ),
            XclXmlUtils::GetStreamName( "../", "comments", mnTab + 1 ),
            rStrm.GetCurrentStream()->getOutputStream(),
            "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml",
            oox::getRelationship(Relationship::COMMENTS));
    rStrm.PushStream( rComments );

    if( rStrm.getVersion() == oox::core::ISOIEC_29500_2008 )
        rComments->startElement( XML_comments,
            XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)),
            FSNS(XML_xmlns, XML_mc), rStrm.getNamespaceURL(OOX_NS(mce)),
            FSNS(XML_xmlns, XML_xdr), rStrm.getNamespaceURL(OOX_NS(dmlSpreadDr)),
            FSNS(XML_xmlns, XML_v2), rStrm.getNamespaceURL(OOX_NS(mceTest)),
            FSNS( XML_mc, XML_Ignorable ), "v2" );
    else
        rComments->startElement( XML_comments,
            XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)),
            FSNS(XML_xmlns, XML_xdr), rStrm.getNamespaceURL(OOX_NS(dmlSpreadDr)) );

    rComments->startElement(XML_authors);

    typedef std::set<OUString> Authors;
    Authors aAuthors;

    size_t nNotes = mrNotes.GetSize();
    for( size_t i = 0; i < nNotes; ++i )
    {
        aAuthors.insert( XclXmlUtils::ToOUString( mrNotes.GetRecord( i )->GetAuthor() ) );
    }

    for( const auto& rAuthor : aAuthors )
    {
        rComments->startElement(XML_author);
        rComments->writeEscaped( rAuthor );
        rComments->endElement( XML_author );
    }

    rComments->endElement( XML_authors );
    rComments->startElement(XML_commentList);

    Authors::const_iterator aAuthorsBegin = aAuthors.begin();
    for( size_t i = 0; i < nNotes; ++i )
    {
        XclExpRecordList< XclExpNote >::RecordRefType xNote = mrNotes.GetRecord( i );
        Authors::const_iterator aAuthor = aAuthors.find(
                XclXmlUtils::ToOUString( xNote->GetAuthor() ) );
        sal_Int32 nAuthorId = distance( aAuthorsBegin, aAuthor );
        xNote->WriteXml( nAuthorId, rStrm );
    }

    rComments->endElement( XML_commentList );
    rComments->endElement( XML_comments );

    rStrm.PopStream();
}

// object manager =============================================================

XclExpObjectManager::XclExpObjectManager( const XclExpRoot& rRoot ) :
    XclExpRoot( rRoot )
{
    InitStream( true );
    assert(mpDffStrm);
    mxEscherEx = std::make_shared<XclEscherEx>( GetRoot(), *this, *mpDffStrm );
}

XclExpObjectManager::XclExpObjectManager( const XclExpObjectManager& rParent ) :
    XclExpRoot( rParent.GetRoot() )
{
    InitStream( false );
    assert(mpDffStrm);
    mxEscherEx = std::make_shared<XclEscherEx>( GetRoot(), *this, *mpDffStrm, rParent.mxEscherEx.get() );
}

XclExpObjectManager::~XclExpObjectManager()
{
}

XclExpDffAnchorBase* XclExpObjectManager::CreateDffAnchor() const
{
    return new XclExpDffSheetAnchor( GetRoot() );
}

rtl::Reference< XclExpRecordBase > XclExpObjectManager::CreateDrawingGroup()
{
    return new XclExpMsoDrawingGroup( *mxEscherEx );
}

void XclExpObjectManager::StartSheet()
{
    mxObjList = new XclExpObjList( GetRoot(), *mxEscherEx );
}

rtl::Reference< XclExpRecordBase > XclExpObjectManager::ProcessDrawing( const SdrPage* pSdrPage )
{
    if( pSdrPage )
        mxEscherEx->AddSdrPage( *pSdrPage, GetOutput() != EXC_OUTPUT_BINARY );
    // the first dummy object may still be open
    OSL_ENSURE( mxEscherEx->GetGroupLevel() <= 1, "XclExpObjectManager::ProcessDrawing - still groups open?" );
    while( mxEscherEx->GetGroupLevel() )
        mxEscherEx->LeaveGroup();
    mxObjList->EndSheet();
    return mxObjList;
}

rtl::Reference< XclExpRecordBase > XclExpObjectManager::ProcessDrawing( const Reference< XShapes >& rxShapes )
{
    if( rxShapes.is() )
        mxEscherEx->AddUnoShapes( rxShapes, GetOutput() != EXC_OUTPUT_BINARY );
    // the first dummy object may still be open
    OSL_ENSURE( mxEscherEx->GetGroupLevel() <= 1, "XclExpObjectManager::ProcessDrawing - still groups open?" );
    while( mxEscherEx->GetGroupLevel() )
        mxEscherEx->LeaveGroup();
    mxObjList->EndSheet();
    return mxObjList;
}

void XclExpObjectManager::EndDocument()
{
    mxEscherEx->EndDocument();
}

XclExpMsoDrawing* XclExpObjectManager::GetMsodrawingPerSheet()
{
    return mxObjList->GetMsodrawingPerSheet();
}

bool XclExpObjectManager::HasObj() const
{
    return !mxObjList->empty();
}

sal_uInt16 XclExpObjectManager::AddObj( std::unique_ptr<XclObj> pObjRec )
{
    return mxObjList->Add( std::move(pObjRec) );
}

std::unique_ptr<XclObj> XclExpObjectManager::RemoveLastObj()
{
    return mxObjList->pop_back();
}

void XclExpObjectManager::InitStream( bool bTempFile )
{
    if( bTempFile )
    {
        moTempFile.emplace();
        mpDffStrm = moTempFile->GetStream( StreamMode::STD_READWRITE );
    }

    if( !mpDffStrm )
    {
        mpBackupStrm = std::make_unique<SvMemoryStream>();
        mpDffStrm = mpBackupStrm.get();
    }

    assert(mpDffStrm);

    mpDffStrm->SetEndian( SvStreamEndian::LITTLE );
}

XclExpEmbeddedObjectManager::XclExpEmbeddedObjectManager(
        const XclExpObjectManager& rParent, const Size& rPageSize, sal_Int32 nScaleX, sal_Int32 nScaleY ) :
    XclExpObjectManager( rParent ),
    maPageSize( rPageSize ),
    mnScaleX( nScaleX ),
    mnScaleY( nScaleY )
{
}

XclExpDffAnchorBase* XclExpEmbeddedObjectManager::CreateDffAnchor() const
{
    return new XclExpDffEmbeddedAnchor( GetRoot(), maPageSize, mnScaleX, mnScaleY );
}

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