/* -*- 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 <memory>
#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
#include <com/sun/star/container/XNamed.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/container/XIndexAccess.hpp>
#include <comphelper/configuration.hxx>
#include <vcl/canvastools.hxx>
#include <vcl/ptrstyle.hxx>
#include <com/sun/star/style/XStyle.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <svl/style.hxx>
#include <editeng/editstat.hxx>
#include <editeng/outlobj.hxx>
#include <sdr/properties/textproperties.hxx>
#include <svx/svdmodel.hxx>
#include <svx/svdotable.hxx>
#include <svx/svdhdl.hxx>
#include "viewcontactoftableobj.hxx"
#include <svx/svdoutl.hxx>
#include <svx/svddrag.hxx>
#include <tablemodel.hxx>
#include <cell.hxx>
#include "tablelayouter.hxx"
#include "tablehandles.hxx"
#include <svx/sdr/table/tabledesign.hxx>
#include <svx/svdundo.hxx>
#include <svx/strings.hrc>
#include <svx/dialmgr.hxx>
#include <editeng/writingmodeitem.hxx>
#include <editeng/frmdiritem.hxx>
#include <libxml/xmlwriter.h>
#include <comphelper/diagnose_ex.hxx>
#include <tools/debug.hxx>

#include <boost/property_tree/ptree.hpp>

#include "sdrtableobjimpl.hxx"

using ::com::sun::star::uno::Any;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::UNO_QUERY;
using ::com::sun::star::uno::UNO_QUERY_THROW;
using ::com::sun::star::uno::Exception;
using ::com::sun::star::container::XIndexAccess;
using ::com::sun::star::style::XStyle;
using ::com::sun::star::table::XTableRows;
using ::com::sun::star::table::XTableColumns;
using ::com::sun::star::table::XTable;
using ::com::sun::star::beans::XPropertySet;
using ::com::sun::star::util::XModifyBroadcaster;
using sdr::properties::TextProperties;
using sdr::properties::BaseProperties;
using namespace ::com::sun::star;
using namespace ::com::sun::star::text;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::style;

namespace sdr::table {

namespace {

class TableProperties : public TextProperties
{
protected:
    // create a new itemset
    SfxItemSet CreateObjectSpecificItemSet(SfxItemPool& rPool) override;

public:
    // basic constructor
    explicit TableProperties(SdrObject& rObj );

    // constructor for copying, but using new object
    TableProperties(const TableProperties& rProps, SdrObject& rObj );

    // Clone() operator, normally just calls the local copy constructor
    std::unique_ptr<BaseProperties> Clone(SdrObject& rObj) const override;

    virtual void ItemChange(const sal_uInt16 nWhich, const SfxPoolItem* pNewItem = nullptr) override;
};

}

TableProperties::TableProperties(SdrObject& rObj)
: TextProperties(rObj)
{
}

TableProperties::TableProperties(const TableProperties& rProps, SdrObject& rObj)
: TextProperties(rProps, rObj)
{
}

std::unique_ptr<BaseProperties> TableProperties::Clone(SdrObject& rObj) const
{
    return std::unique_ptr<BaseProperties>(new TableProperties(*this, rObj));
}

void TableProperties::ItemChange(const sal_uInt16 nWhich, const SfxPoolItem* pNewItem)
{
    if( nWhich == SDRATTR_TEXTDIRECTION )
        AttributeProperties::ItemChange( nWhich, pNewItem );
    else
        TextProperties::ItemChange( nWhich, pNewItem );
}

// create a new itemset
SfxItemSet TableProperties::CreateObjectSpecificItemSet(SfxItemPool& rPool)
{
    return SfxItemSet(rPool,

        // range from SdrAttrObj
        svl::Items<SDRATTR_START, SDRATTR_SHADOW_LAST,
        SDRATTR_MISC_FIRST, SDRATTR_MISC_LAST,
        SDRATTR_TEXTDIRECTION, SDRATTR_TEXTDIRECTION,

        // range for SdrTableObj
        SDRATTR_TABLE_FIRST, SDRATTR_TABLE_LAST,

        // range from SdrTextObj
        EE_ITEMS_START, EE_ITEMS_END>);
}

namespace {

class TableObjectGeoData : public SdrTextObjGeoData
{
public:
    tools::Rectangle   maLogicRect;
};

}

TableStyleSettings::TableStyleSettings()
: mbUseFirstRow(true)
, mbUseLastRow(false)
, mbUseFirstColumn(false)
, mbUseLastColumn(false)
, mbUseRowBanding(true)
, mbUseColumnBanding(false)
{
}

TableStyleSettings::TableStyleSettings( const TableStyleSettings& rStyle )
{
    (*this) = rStyle;
}

TableStyleSettings& TableStyleSettings::operator=(const TableStyleSettings& rStyle)
{
    mbUseFirstRow = rStyle.mbUseFirstRow;
    mbUseLastRow = rStyle.mbUseLastRow;
    mbUseFirstColumn = rStyle.mbUseFirstColumn;
    mbUseLastColumn = rStyle.mbUseLastColumn;
    mbUseRowBanding = rStyle.mbUseRowBanding;
    mbUseColumnBanding = rStyle.mbUseColumnBanding;
    return *this;
}

bool TableStyleSettings::operator==( const TableStyleSettings& rStyle ) const
{
    return
        (mbUseFirstRow == rStyle.mbUseFirstRow) &&
        (mbUseLastRow == rStyle.mbUseLastRow) &&
        (mbUseFirstColumn == rStyle.mbUseFirstColumn) &&
        (mbUseLastColumn == rStyle.mbUseLastColumn) &&
        (mbUseRowBanding == rStyle.mbUseRowBanding) &&
        (mbUseColumnBanding == rStyle.mbUseColumnBanding);
}


SdrTableObjImpl* SdrTableObjImpl::lastLayoutTable = nullptr;
tools::Rectangle SdrTableObjImpl::lastLayoutInputRectangle;
tools::Rectangle SdrTableObjImpl::lastLayoutResultRectangle;
bool SdrTableObjImpl::lastLayoutFitWidth;
bool SdrTableObjImpl::lastLayoutFitHeight;
WritingMode SdrTableObjImpl::lastLayoutMode;
sal_Int32 SdrTableObjImpl::lastRowCount;
sal_Int32 SdrTableObjImpl::lastColCount;
bool SdrTableObjImpl::rowSizeChanged = false;
std::vector<sal_Int32> SdrTableObjImpl::lastColWidths;

SdrTableObjImpl::SdrTableObjImpl()
: mpTableObj( nullptr )
, mbSkipChangeLayout(false)
{
}


SdrTableObjImpl::~SdrTableObjImpl()
{
    if( lastLayoutTable == this )
        lastLayoutTable = nullptr;
}


void SdrTableObjImpl::CropTableModelToSelection(const CellPos& rStart, const CellPos& rEnd)
{
    if(!mxTable.is())
    {
        return;
    }

    const sal_Int32 nColumns(rEnd.mnCol - rStart.mnCol + 1);
    const sal_Int32 nRows(rEnd.mnRow - rStart.mnRow + 1);

    if(nColumns < 1 || nRows < 1 || nColumns > getColumnCount() || nRows > getRowCount())
    {
        return;
    }

    // tdf#116977 First thought was to create the new TableModel, copy data to it and then exchange
    // mxTable and dispose old one. This does *not* work, even when all stuff looks nicely referenced
    // and safe *because* Cell::create gets handed over the current SdrTableObj, hands it to
    // ::Cell and there the local mxTable is initialized using rTableObj.getTable() (!). Due to This,
    // the new created Cells in a new created TableModel based on given mpTableObj *will be disposed*
    // when the old mxTable gets disposed - ARGH!
    // To avoid, change strategy: Remember old TableModel, reset mxTable immediately - this is the
    // SdrTableObjImpl of the current SdrTableObj anyways. Luckily, this works as intended...

    // remember old TableModel
    TableModelRef xOldTable(mxTable);

    // immediately create new one and initialize. This creates ::Cell's which then will use
    // the correct TableModel (accessed through SdrTableObj, but using local mxTable)
    mxTable = new TableModel(mpTableObj);
    mxTable->init(nColumns, nRows);

    // copy cells
    for( sal_Int32 nRow = 0; nRow < nRows; ++nRow )
    {
        for( sal_Int32 nCol = 0; nCol < nColumns; ++nCol ) try
        {
            CellRef xTargetCell( mxTable->getCell( nCol, nRow ) );
            if( xTargetCell.is() )
                xTargetCell->cloneFrom( xOldTable->getCell( rStart.mnCol + nCol, rStart.mnRow + nRow ) );
        }
        catch( Exception& )
        {
            TOOLS_WARN_EXCEPTION("svx.table", "");
        }
    }

    // copy row heights
    Reference< XTableRows > xNewRows(mxTable->getRows(), css::uno::UNO_SET_THROW );
    static constexpr OUStringLiteral sHeight( u"Height" );
    for( sal_Int32 nRow = 0; nRow < nRows; ++nRow )
    {
        Reference< XPropertySet > xNewSet( xNewRows->getByIndex( nRow ), UNO_QUERY_THROW );
        xNewSet->setPropertyValue( sHeight, Any( mpLayouter->getRowHeight( rStart.mnRow + nRow ) ) );
    }

    // copy column widths
    Reference< XTableColumns > xNewColumns( mxTable->getColumns(), css::uno::UNO_SET_THROW );
    static constexpr OUStringLiteral sWidth( u"Width" );
    for( sal_Int32 nCol = 0; nCol < nColumns; ++nCol )
    {
        Reference< XPropertySet > xNewSet( xNewColumns->getByIndex( nCol ), UNO_QUERY_THROW );
        xNewSet->setPropertyValue( sWidth, Any( mpLayouter->getColumnWidth( rStart.mnCol + nCol ) ) );
    }

    // reset layouter which still holds a copy to old TableModel
    mpLayouter.reset();

    // cleanup old TableModel
    {
        Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) );
        xOldTable->removeModifyListener( xListener );
        xOldTable->dispose();
        xOldTable.clear();
    }

    // create and hand over to new TableLayouter
    mpLayouter.reset(new TableLayouter( mxTable ));

    // add needed listener to react on changes
    Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) );
    mxTable->addModifyListener( xListener );

    // Apply Style to Cells
    ApplyCellStyles();

    // layout cropped table
    auto aRectangle = mpTableObj->getRectangle();
    LayoutTable(aRectangle, false, false);
    mpTableObj->setRectangle(aRectangle);
}

void SdrTableObjImpl::init( SdrTableObj* pTable, sal_Int32 nColumns, sal_Int32 nRows )
{
    mpTableObj = pTable;
    mxTable = new TableModel( pTable );
    mxTable->init( nColumns, nRows );
    Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) );
    mxTable->addModifyListener( xListener );
    mpLayouter.reset(new TableLayouter( mxTable ));
    auto aRectangle = mpTableObj->getRectangle();
    LayoutTable(aRectangle, true, true);
    mpTableObj->setRectangle(aRectangle);
    mpTableObj->maLogicRect = aRectangle;
}


SdrTableObjImpl& SdrTableObjImpl::operator=( const SdrTableObjImpl& rSource )
{
    if(this == &rSource)
    {
        return *this;
    }

    if(nullptr == mpTableObj || nullptr == rSource.mpTableObj)
    {
        // error: need both SdrObjects to successfully copy data
        return *this;
    }

    // remove evtl. listeners from local
    disconnectTableStyle();

    // reset layouter which holds a copy
    mpLayouter.reset();

    // cleanup local mxTable if used
    if( mxTable.is() )
    {
        Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) );
        mxTable->removeModifyListener( xListener );
        mxTable->dispose();
        mxTable.clear();
    }

    // tdf#127481: reset active cell reference
    mxActiveCell.clear();

    // copy TableStyle (short internal data)
    maTableStyle = rSource.maTableStyle;

    // create/copy new mxTable. This will copy all needed cells, too
    mxTable = new TableModel( mpTableObj, rSource.mxTable );

    // create and hand over to new TableLayouter
    mpLayouter.reset(new TableLayouter( mxTable ));

    // add needed listener to react on changes
    Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) );
    mxTable->addModifyListener( xListener );

    // handle TableStyle
    Reference< XIndexAccess > xNewTableStyle;
    SdrModel& rSourceSdrModel(rSource.mpTableObj->getSdrModelFromSdrObject());
    SdrModel& rTargetSdrModel(mpTableObj->getSdrModelFromSdrObject());

    if(rSource.mxTableStyle.is() && &rSourceSdrModel == &rTargetSdrModel)
    {
        // source and target model the same -> keep current TableStyle
        xNewTableStyle = rSource.mxTableStyle;
    }

    if(!xNewTableStyle.is() && rSource.mxTableStyle.is()) try
    {
        // search in target SdrModel for that TableStyle
        const OUString sStyleName( Reference< XNamed >( rSource.mxTableStyle, UNO_QUERY_THROW )->getName() );
        Reference< XStyleFamiliesSupplier > xSFS(rTargetSdrModel.getUnoModel(), UNO_QUERY_THROW );
        Reference< XNameAccess > xFamilyNameAccess( xSFS->getStyleFamilies(), css::uno::UNO_SET_THROW );
        Reference< XNameAccess > xTableFamilyAccess( xFamilyNameAccess->getByName( u"table"_ustr ), UNO_QUERY_THROW );

        if( xTableFamilyAccess->hasByName( sStyleName ) )
        {
            // found table style with the same name
            xTableFamilyAccess->getByName( sStyleName ) >>= xNewTableStyle;
        }
        else
        {
            // copy or? Not found, use 1st existing TableStyle (or none)
            Reference< XIndexAccess > xIndexAccess( xTableFamilyAccess, UNO_QUERY_THROW );
            xIndexAccess->getByIndex( 0 ) >>= xNewTableStyle;
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION("svx.table", "");
    }

    // set that TableStyle
    mxTableStyle = std::move(xNewTableStyle);

    // Apply Style to Cells
    ApplyCellStyles();

    // copy geometry
    mpTableObj->setRectangle(mpTableObj->maLogicRect);

    // layout cloned table
    auto aRectangle = mpTableObj->getRectangle();
    LayoutTable(aRectangle, false, false);
    mpTableObj->setRectangle(aRectangle);

    // re-connect to styles (evtl. in new SdrModel)
    connectTableStyle();

    return *this;
}

void SdrTableObjImpl::ApplyCellStyles()
{
    if( !mxTable.is() || !mxTableStyle.is() )
        return;

    const sal_Int32 nColCount = getColumnCount();
    const sal_Int32 nRowCount = getRowCount();

    const TableStyleSettings& rStyle = maTableStyle;

    CellPos aPos;
    for( aPos.mnRow = 0; aPos.mnRow < nRowCount; ++aPos.mnRow )
    {
        const bool bFirstRow = (aPos.mnRow == 0) && rStyle.mbUseFirstRow;
        const bool bLastRow = (aPos.mnRow == nRowCount-1) && rStyle.mbUseLastRow;

        for( aPos.mnCol = 0; aPos.mnCol < nColCount; ++aPos.mnCol )
        {
            Reference< XStyle > xStyle;

            // first and last row win first, if used and available
            if( bFirstRow )
            {
                mxTableStyle->getByIndex(first_row_style) >>= xStyle;
            }
            else if( bLastRow )
            {
                mxTableStyle->getByIndex(last_row_style) >>= xStyle;
            }

            if( !xStyle.is() )
            {
                // next come first and last column, if used and available
                if( rStyle.mbUseFirstColumn && (aPos.mnCol == 0)  )
                {
                    mxTableStyle->getByIndex(first_column_style) >>= xStyle;
                }
                else if( rStyle.mbUseLastColumn && (aPos.mnCol == nColCount-1) )
                {
                    mxTableStyle->getByIndex(last_column_style) >>= xStyle;
                }
            }

            if( !xStyle.is() && rStyle.mbUseRowBanding )
            {
                if( (aPos.mnRow & 1) == 0 )
                {
                    mxTableStyle->getByIndex(even_rows_style) >>= xStyle;
                }
                else
                {
                    mxTableStyle->getByIndex(odd_rows_style) >>= xStyle;
                }
            }

            if( !xStyle.is() && rStyle.mbUseColumnBanding )
            {
                if( (aPos.mnCol & 1) == 0 )
                {
                    mxTableStyle->getByIndex(even_columns_style) >>= xStyle;
                }
                else
                {
                    mxTableStyle->getByIndex(odd_columns_style) >>= xStyle;
                }
            }

            if( !xStyle.is() )
            {
                // use default cell style if non found yet
                mxTableStyle->getByIndex(body_style) >>= xStyle;
            }


            if( xStyle.is() )
            {
                SfxUnoStyleSheet* pStyle = SfxUnoStyleSheet::getUnoStyleSheet(xStyle);

                if( pStyle )
                {
                    CellRef xCell( getCell( aPos ) );
                    if( xCell.is() && ( xCell->GetStyleSheet() != pStyle ) )
                    {
                        xCell->SetStyleSheet( pStyle, true );
                    }
                }
            }
        }
    }
}


void SdrTableObjImpl::dispose()
{
    disconnectTableStyle();
    mxTableStyle.clear();

    mpLayouter.reset();

    if( mxTable.is() )
    {
        Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) );
        mxTable->removeModifyListener( xListener );
        mxTable->dispose();
        mxTable.clear();
    }
}


void SdrTableObjImpl::DragEdge( bool mbHorizontal, int nEdge, sal_Int32 nOffset )
{
    if( !((nEdge >= 0) && mxTable.is()))
        return;

    try
    {
        static constexpr OUString sSize( u"Size"_ustr );
        if( mbHorizontal )
        {
            if (nEdge <= getRowCount())
            {
                sal_Int32 nHeight = mpLayouter->getRowHeight( (!nEdge)?nEdge:(nEdge-1) );
                if(nEdge==0)
                    nHeight -= nOffset;
                else
                    nHeight += nOffset;
                Reference< XIndexAccess > xRows( mxTable->getRows(), UNO_QUERY_THROW );
                Reference< XPropertySet > xRowSet( xRows->getByIndex( (!nEdge)?nEdge:(nEdge-1) ), UNO_QUERY_THROW );
                xRowSet->setPropertyValue( sSize, Any( nHeight ) );
                rowSizeChanged = true;
            }
        }
        else
        {
            /*
            fixes fdo#59889 and resizing of table in edge dragging
            Total vertical edges in a NxN table is N+1, indexed from 0 to N and total Columns is N, indexed from 0 to N-1
            In LTR table vertical edge responsible for dragging of column x(x=0 to N-1) is, Edge x+1
            But in RTL table vertical edge responsible for dragging of column x(x=0 to N-1, but from right to left)is, Edge x
            In LTR table dragging of edge 0(for RTL table edge N) does nothing.
            */
            //Todo: Implement Dragging functionality for leftmost edge of table.
            if (nEdge <= getColumnCount())
            {
                const bool bRTL = mpTableObj != nullptr && (mpTableObj->GetWritingMode() == WritingMode_RL_TB);
                sal_Int32 nWidth;
                if(bRTL)
                {
                    nWidth = mpLayouter->getColumnWidth( nEdge );
                }
                else
                {
                    nWidth = mpLayouter->getColumnWidth( (!nEdge)?nEdge:(nEdge-1) );
                }
                Reference< XIndexAccess > xCols( mxTable->getColumns(), UNO_QUERY_THROW );
                nWidth += nOffset;
                if(bRTL && nEdge<getColumnCount())
                {
                    Reference< XPropertySet > xColSet( xCols->getByIndex( nEdge ), UNO_QUERY_THROW );
                    xColSet->setPropertyValue( sSize, Any( nWidth ) );
                }
                else if(!bRTL && nEdge>0)
                {
                    Reference< XPropertySet > xColSet( xCols->getByIndex( nEdge-1 ), UNO_QUERY_THROW );
                    xColSet->setPropertyValue( sSize, Any( nWidth ) );
                }
                /* To prevent the table resizing on edge dragging */
                if( nEdge > 0 && nEdge < mxTable->getColumnCount() )
                {
                    if( bRTL )
                        nEdge--;

                    nWidth = mpLayouter->getColumnWidth(nEdge);
                    nWidth = std::max(static_cast<sal_Int32>(nWidth - nOffset), sal_Int32(0));

                    Reference<XPropertySet> xColSet(xCols->getByIndex(nEdge), UNO_QUERY_THROW);
                    xColSet->setPropertyValue(sSize, Any(nWidth));
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION("svx.table", "");
    }
}


// XModifyListener


void SAL_CALL SdrTableObjImpl::modified( const css::lang::EventObject& aEvent )
{
    if (aEvent.Source == mxTableStyle && mpTableObj)
        static_cast<TextProperties&>(mpTableObj->GetProperties()).increaseVersion();

    update();
}

void SdrTableObjImpl::update()
{
    // source can be the table model itself or the assigned table template
    TableModelNotifyGuard aGuard( mxTable.get() );
    if( !mpTableObj )
        return;

    if( (maEditPos.mnRow >= getRowCount()) || (maEditPos.mnCol >= getColumnCount()) || (getCell( maEditPos ) != mxActiveCell) )
    {
        if(maEditPos.mnRow >= getRowCount())
            maEditPos.mnRow = getRowCount()-1;

        if(maEditPos.mnCol >= getColumnCount())
            maEditPos.mnCol = getColumnCount()-1;

        mpTableObj->setActiveCell( maEditPos );
    }

    ApplyCellStyles();

    mpTableObj->setRectangle(mpTableObj->maLogicRect);
    auto aRectangle = mpTableObj->getRectangle();
    LayoutTable(aRectangle, false, false);
    mpTableObj->setRectangle(aRectangle);

    mpTableObj->SetBoundAndSnapRectsDirty();
    mpTableObj->ActionChanged();
    mpTableObj->BroadcastObjectChange();
}


void SdrTableObjImpl::connectTableStyle()
{
    if( mxTableStyle.is() )
    {
        Reference< XModifyBroadcaster > xBroadcaster( mxTableStyle, UNO_QUERY );
        if( xBroadcaster.is() )
        {
            Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) );
            xBroadcaster->addModifyListener( xListener );
        }
    }
}


void SdrTableObjImpl::disconnectTableStyle()
{
    if( mxTableStyle.is() )
    {
        Reference< XModifyBroadcaster > xBroadcaster( mxTableStyle, UNO_QUERY );
        if( xBroadcaster.is() )
        {
            Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) );
            xBroadcaster->removeModifyListener( xListener );
        }
    }
}


bool SdrTableObjImpl::isInUse()
{
    return mpTableObj && mpTableObj->IsInserted();
}

void SdrTableObjImpl::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrTableObjImpl"));
    if (mpLayouter)
        mpLayouter->dumpAsXml(pWriter);
    mxTable->dumpAsXml(pWriter);
    (void)xmlTextWriterEndElement(pWriter);
}


// XEventListener


void SAL_CALL SdrTableObjImpl::disposing( const css::lang::EventObject& Source )
{
    assert(Source.Source == mxTableStyle);
    (void)Source;

    Reference<XIndexAccess> xDefaultStyle;
    try
    {
        Reference<XStyleFamiliesSupplier> xSupplier(mpTableObj->getSdrModelFromSdrObject().getUnoModel(), UNO_QUERY_THROW);
        Reference<XNameAccess> xTableFamily(xSupplier->getStyleFamilies()->getByName(u"table"_ustr), UNO_QUERY_THROW);
        xDefaultStyle.set(xTableFamily->getByName(u"default"_ustr), UNO_QUERY_THROW);
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION("svx.table", "");
    }

    mpTableObj->setTableStyle(xDefaultStyle);
}


CellRef SdrTableObjImpl::getCell(  const CellPos& rPos  ) const
{
    CellRef xCell;
    if( mxTable.is() ) try
    {
        xCell = mxTable->getCell( rPos.mnCol, rPos.mnRow );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION("svx.table", "");
    }
    return xCell;
}


sal_Int32 SdrTableObjImpl::getColumnCount() const
{
    return mxTable.is() ? mxTable->getColumnCount() : 0;
}

std::vector<sal_Int32> SdrTableObjImpl::getColumnWidths() const
{
    std::vector<sal_Int32> aRet;

    if (mxTable.is())
        aRet = mxTable->getColumnWidths();

    return aRet;
}

sal_Int32 SdrTableObjImpl::getRowCount() const
{
    return mxTable.is() ? mxTable->getRowCount() : 0;
}

void SdrTableObjImpl::LayoutTable( tools::Rectangle& rArea, bool bFitWidth, bool bFitHeight )
{
    if (comphelper::IsFuzzing())
        return;
    if(!mpLayouter)
        return;

    // Optimization: SdrTableObj::SetChanged() can call this very often, repeatedly
    // with the same settings, noticeably increasing load time. Skip if already done.
    bool bInteractiveMightGrowBecauseTextChanged =
        mpTableObj->IsReallyEdited() && (mpTableObj->IsAutoGrowHeight() || mpTableObj->IsAutoGrowWidth());
    WritingMode writingMode = mpTableObj->GetWritingMode();
    if( bInteractiveMightGrowBecauseTextChanged
        || lastLayoutTable != this || lastLayoutInputRectangle != rArea
        || lastLayoutFitWidth != bFitWidth || lastLayoutFitHeight != bFitHeight
        || lastLayoutMode != writingMode
        || lastRowCount != getRowCount()
        || lastColCount != getColumnCount()
        || lastColWidths != getColumnWidths()
        || rowSizeChanged )
    {
        lastLayoutTable = this;
        lastLayoutInputRectangle = rArea;
        lastLayoutFitWidth = bFitWidth;
        lastLayoutFitHeight = bFitHeight;
        lastLayoutMode = writingMode;
        lastRowCount = getRowCount();
        lastColCount = getColumnCount();
        // Column resize, when the total width and column count of the
        // table is unchanged, but re-layout is still needed.
        lastColWidths = getColumnWidths();
        TableModelNotifyGuard aGuard( mxTable.get() );
        mpLayouter->LayoutTable( rArea, bFitWidth, bFitHeight );
        lastLayoutResultRectangle = rArea;
        rowSizeChanged = false;
    }
    else
    {
        rArea = lastLayoutResultRectangle;
        mpLayouter->UpdateBorderLayout();
    }
}

void SdrTableObjImpl::UpdateCells( tools::Rectangle const & rArea )
{
    if( mpLayouter && mxTable.is() )
    {
        TableModelNotifyGuard aGuard( mxTable.get() );
        mpLayouter->updateCells( rArea );
        mxTable->setModified(true);
    }
}


// BaseProperties section


std::unique_ptr<sdr::properties::BaseProperties> SdrTableObj::CreateObjectSpecificProperties()
{
    return std::make_unique<TableProperties>(*this);
}


// DrawContact section


std::unique_ptr<sdr::contact::ViewContact> SdrTableObj::CreateObjectSpecificViewContact()
{
    return std::make_unique<sdr::contact::ViewContactOfTableObj>(*this);
}

SdrTableObj::SdrTableObj(SdrModel& rSdrModel)
:   SdrTextObj(rSdrModel)
{
    osl_atomic_increment(&m_refCount); // other I get deleted during construction
    init( 1, 1 );
    osl_atomic_decrement(&m_refCount);
}

SdrTableObj::SdrTableObj(SdrModel& rSdrModel, SdrTableObj const & rSource)
:   SdrTextObj(rSdrModel, rSource)
{
    osl_atomic_increment(&m_refCount);

    init( 1, 1 );

    TableModelNotifyGuard aGuard( mpImpl.is() ? mpImpl->mxTable.get() : nullptr );

    maLogicRect = rSource.maLogicRect;
    maRectangle = rSource.maRectangle;
    maGeo = rSource.maGeo;
    meTextKind = rSource.meTextKind;
    mbTextFrame = rSource.mbTextFrame;
    maTextSize = rSource.maTextSize;
    mbTextSizeDirty = rSource.mbTextSizeDirty;
    mbNoShear = rSource.mbNoShear;
    mbDisableAutoWidthOnDragging = rSource.mbDisableAutoWidthOnDragging;

    // use SdrTableObjImpl::operator= now to
    // copy model data and other stuff (see there)
    *mpImpl = *rSource.mpImpl;

    osl_atomic_decrement(&m_refCount);
}

SdrTableObj::SdrTableObj(
    SdrModel& rSdrModel,
    const ::tools::Rectangle& rNewRect,
    sal_Int32 nColumns,
    sal_Int32 nRows)
:   SdrTextObj(rSdrModel, rNewRect)
    ,maLogicRect(rNewRect)
{
    osl_atomic_increment(&m_refCount);

    if( nColumns <= 0 )
        nColumns = 1;

    if( nRows <= 0 )
        nRows = 1;

    init( nColumns, nRows );

    osl_atomic_decrement(&m_refCount);
}


void SdrTableObj::init( sal_Int32 nColumns, sal_Int32 nRows )
{
    m_bClosedObj = true;

    mpImpl = new SdrTableObjImpl;
    mpImpl->init( this, nColumns, nRows );

    // Stuff done from old SetModel:
    if( !maLogicRect.IsEmpty() )
    {
        setRectangle(maLogicRect);
        auto aRectangle = getRectangle();
        mpImpl->LayoutTable(aRectangle, false, false);
        setRectangle(aRectangle);
    }
}


SdrTableObj::~SdrTableObj()
{
    mpImpl->dispose();
}


// table stuff


Reference< XTable > SdrTableObj::getTable() const
{
    return mpImpl->mxTable;
}

const rtl::Reference< TableModel > & SdrTableObj::getUnoTable() const
{
    return mpImpl->mxTable;
}

bool SdrTableObj::isValid( const CellPos& rPos ) const
{
    return (rPos.mnCol >= 0) && (rPos.mnCol < mpImpl->getColumnCount()) && (rPos.mnRow >= 0) && (rPos.mnRow < mpImpl->getRowCount());
}


CellPos SdrTableObj::getFirstCell()
{
    return CellPos( 0,0 );
}


CellPos SdrTableObj::getLastCell() const
{
    CellPos aPos;
    if( mpImpl->mxTable.is() )
    {
        aPos.mnCol = mpImpl->getColumnCount()-1;
        aPos.mnRow = mpImpl->getRowCount()-1;
    }
    return aPos;
}


CellPos SdrTableObj::getLeftCell( const CellPos& rPos, bool bEdgeTravel ) const
{
    switch( GetWritingMode() )
    {
    default:
    case WritingMode_LR_TB:
        return getPreviousCell( rPos, bEdgeTravel );
    case WritingMode_RL_TB:
        return getNextCell( rPos, bEdgeTravel );
    case WritingMode_TB_RL:
        return getPreviousRow( rPos, bEdgeTravel );
    }
}


CellPos SdrTableObj::getRightCell( const CellPos& rPos, bool bEdgeTravel  ) const
{
    switch( GetWritingMode() )
    {
    default:
    case WritingMode_LR_TB:
        return getNextCell( rPos, bEdgeTravel );
    case WritingMode_RL_TB:
        return getPreviousCell( rPos, bEdgeTravel );
    case WritingMode_TB_RL:
        return getNextRow( rPos, bEdgeTravel );
    }
}


CellPos SdrTableObj::getUpCell( const CellPos& rPos, bool bEdgeTravel ) const
{
    switch( GetWritingMode() )
    {
    default:
    case WritingMode_LR_TB:
    case WritingMode_RL_TB:
        return getPreviousRow( rPos, bEdgeTravel );
    case WritingMode_TB_RL:
        return getPreviousCell( rPos, bEdgeTravel );
    }
}


CellPos SdrTableObj::getDownCell( const CellPos& rPos, bool bEdgeTravel ) const
{
    switch( GetWritingMode() )
    {
    default:
    case WritingMode_LR_TB:
    case WritingMode_RL_TB:
        return getNextRow( rPos, bEdgeTravel );
    case WritingMode_TB_RL:
        return getNextCell( rPos, bEdgeTravel );
    }
}


CellPos SdrTableObj::getPreviousCell( const CellPos& rPos, bool bEdgeTravel ) const
{
    CellPos aPos( rPos );
    if( mpImpl.is() )
    {
        CellRef xCell( mpImpl->getCell( aPos ) );
        if( xCell.is() && xCell->isMerged() )
        {
            sal_Int32 nTemp = 0;
            findMergeOrigin( mpImpl->mxTable, aPos.mnCol, aPos.mnRow, aPos.mnCol, nTemp );
        }

        if( aPos.mnCol > 0 )
        {
            --aPos.mnCol;
        }

        else if( bEdgeTravel && (aPos.mnRow > 0) )
        {
            aPos.mnCol = mpImpl->mxTable->getColumnCount()-1;
            --aPos.mnRow;
        }
    }
    return aPos;
}


CellPos SdrTableObj::getNextCell( const CellPos& rPos, bool bEdgeTravel ) const
{
    CellPos aPos( rPos );
    if( mpImpl.is() )
    {
        CellRef xCell( mpImpl->getCell( aPos ) );
        if( xCell.is() )
        {
            if( xCell->isMerged() )
            {
                findMergeOrigin( mpImpl->mxTable, aPos.mnCol, aPos.mnRow, aPos.mnCol, aPos.mnRow );

                xCell = mpImpl->getCell(aPos);

                if( xCell.is() )
                {
                    aPos.mnCol += xCell->getColumnSpan();
                    aPos.mnRow = rPos.mnRow;
                }
            }
            else
            {
                aPos.mnCol += xCell->getColumnSpan();
            }

            if( aPos.mnCol < mpImpl->mxTable->getColumnCount() )
                return aPos;

            if( bEdgeTravel && ((aPos.mnRow + 1) < mpImpl->getRowCount()) )
            {
                aPos.mnCol = 0;
                aPos.mnRow += 1;
                return aPos;
            }
        }
    }

    // last cell reached, no traveling possible
    return rPos;
}


CellPos SdrTableObj::getPreviousRow( const CellPos& rPos, bool bEdgeTravel ) const
{
    CellPos aPos( rPos );
    if( mpImpl.is() )
    {
        CellRef xCell( mpImpl->getCell( aPos ) );
        if( xCell.is() && xCell->isMerged() )
        {
            sal_Int32 nTemp = 0;
            findMergeOrigin( mpImpl->mxTable, aPos.mnCol, aPos.mnRow, nTemp, aPos.mnRow );
        }

        if( aPos.mnRow > 0 )
        {
            --aPos.mnRow;
        }
        else if( bEdgeTravel && (aPos.mnCol > 0) )
        {
            aPos.mnRow = mpImpl->mxTable->getRowCount()-1;
            --aPos.mnCol;
        }
    }
    return aPos;
}


CellPos SdrTableObj::getNextRow( const CellPos& rPos, bool bEdgeTravel ) const
{
    CellPos aPos( rPos );

    if( mpImpl.is() )
    {
        CellRef xCell( mpImpl->getCell( rPos ) );
        if( xCell.is() )
        {
            if( xCell->isMerged() )
            {
                findMergeOrigin( mpImpl->mxTable, aPos.mnCol, aPos.mnRow, aPos.mnCol, aPos.mnRow );
                xCell = mpImpl->getCell(aPos);
                aPos.mnCol = rPos.mnCol;
            }

            if( xCell.is() )
                aPos.mnRow += xCell->getRowSpan();

            if( aPos.mnRow < mpImpl->mxTable->getRowCount() )
                return aPos;

            if( bEdgeTravel && (aPos.mnCol + 1) < mpImpl->mxTable->getColumnCount() )
            {
                aPos.mnRow = 0;
                aPos.mnCol += 1;

                while( aPos.mnCol < mpImpl->mxTable->getColumnCount() )
                {
                    xCell = mpImpl->getCell( aPos );
                    if( xCell.is() && !xCell->isMerged() )
                        return aPos;
                    aPos.mnCol += 1;
                }
            }
        }
    }

    // last position reached, no more traveling possible
    return rPos;
}


const TableStyleSettings& SdrTableObj::getTableStyleSettings() const
{
    if( mpImpl.is())
    {
        return mpImpl->maTableStyle;
    }
    else
    {
        static TableStyleSettings aTmp;
        return aTmp;
    }
}


void SdrTableObj::setTableStyleSettings( const TableStyleSettings& rStyle )
{
    if( mpImpl.is() )
    {
        mpImpl->maTableStyle = rStyle;
        mpImpl->update();
    }
}


TableHitKind SdrTableObj::CheckTableHit( const Point& rPos, sal_Int32& rnX, sal_Int32& rnY, const sal_uInt16 aTol ) const
{
    if( !mpImpl.is() || !mpImpl->mxTable.is() )
        return TableHitKind::NONE;

    rnX = 0;
    rnY = 0;

    const sal_Int32 nColCount = mpImpl->getColumnCount();
    const sal_Int32 nRowCount = mpImpl->getRowCount();

    sal_Int32 nX = rPos.X() - getRectangle().Left();
    sal_Int32 nY = rPos.Y() - getRectangle().Top();

    if( (nX < 0) || (nX > getRectangle().GetWidth()) || (nY < 0) || (nY > getRectangle().GetHeight() ) )
        return TableHitKind::NONE;

    // get vertical edge number and check for a hit
    const bool bRTL = (GetWritingMode() == WritingMode_RL_TB);
    bool bVrtHit = false;
    if( !bRTL )
    {
        while( rnX <= nColCount )
        {
            if( nX - aTol <= 0 )
            {
                bVrtHit = true;
                break;
            }

            if( rnX == nColCount )
                break;

            nX -= mpImpl->mpLayouter->getColumnWidth( rnX );
            if( nX < 0 )
                break;
            rnX++;
        }
    }
    else
    {
        rnX = nColCount;
        while( rnX >= 0 )
        {
            if( nX - aTol <= 0 )
            {
                bVrtHit = true;
                break;
            }

            if( rnX == 0 )
                break;

            rnX--;
            nX -= mpImpl->mpLayouter->getColumnWidth( rnX );
            if( nX < 0 )
                break;
        }
    }

    // rnX is now the edge number left to the pointer, if it was hit bHrzHit is also true

    // get vertical edge number and check for a hit
    bool bHrzHit = false;
    while( rnY <= nRowCount )
    {
        if( nY - aTol <= 0 )
        {
            bHrzHit = true;
            break;
        }

        if( rnY == nRowCount )
            break;

        nY -= mpImpl->mpLayouter->getRowHeight(rnY);
        if( nY < 0 )
            break;
        rnY++;
    }

    // rnY is now the edge number above the pointer, if it was hit bVrtHit is also true

    if( bVrtHit && mpImpl->mpLayouter->isEdgeVisible( rnX, rnY, false ) )
        return TableHitKind::VerticallBorder;

    if( bHrzHit && mpImpl->mpLayouter->isEdgeVisible( rnX, rnY, true ) )
        return TableHitKind::HorizontalBorder;

    CellRef xCell( mpImpl->getCell( CellPos( rnX, rnY ) ) );
    if( xCell.is() && xCell->isMerged() )
        findMergeOrigin( mpImpl->mxTable, rnX, rnY, rnX, rnY );

    if( xCell.is() )
    {
        nX += mpImpl->mpLayouter->getColumnWidth( rnX );
        //Fix for fdo#62673 : non-editable cell in table on cell merge
        sal_Int32 i=0;
        while(xCell.is() && xCell->isMerged())
        {
            nX += mpImpl->mpLayouter->getColumnWidth( rnX+i );
            i++;
            if(rnX+i < nColCount)
                xCell=mpImpl->getCell( CellPos( rnX+i, rnY) );
            else
                break;
        }

        if( nX < xCell->GetTextLeftDistance() )
            return TableHitKind::Cell;
    }

    return TableHitKind::CellTextArea;
}

const SfxItemSet& SdrTableObj::GetActiveCellItemSet() const
{
    return getActiveCell()->GetItemSet();
}

void SdrTableObj::setTableStyle( const Reference< XIndexAccess >& xTableStyle )
{
    if( mpImpl.is() && (mpImpl->mxTableStyle != xTableStyle) )
    {
        mpImpl->disconnectTableStyle();
        mpImpl->mxTableStyle = xTableStyle;
        mpImpl->connectTableStyle();
        mpImpl->update();
    }
}


const Reference< XIndexAccess >& SdrTableObj::getTableStyle() const
{
    if( mpImpl.is() )
    {
        return mpImpl->mxTableStyle;
    }
    else
    {
        static Reference< XIndexAccess > aTmp;
        return aTmp;
    }
}


// text stuff


/** returns the currently active text. */
SdrText* SdrTableObj::getActiveText() const
{
    return getActiveCell().get();
}


/** returns the nth available text. */
SdrText* SdrTableObj::getText( sal_Int32 nIndex ) const
{
    if( mpImpl->mxTable.is() )
    {
        const sal_Int32 nColCount = mpImpl->getColumnCount();
        if( nColCount )
        {
            CellPos aPos( nIndex % nColCount, nIndex / nColCount );

            CellRef xCell( mpImpl->getCell( aPos ) );
            return xCell.get();
        }
    }
    return nullptr;
}


/** returns the number of texts available for this object. */
sal_Int32 SdrTableObj::getTextCount() const
{
    if( mpImpl->mxTable.is() )
    {
        const sal_Int32 nColCount = mpImpl->getColumnCount();
        const sal_Int32 nRowCount = mpImpl->getRowCount();

        return nColCount * nRowCount;
    }
    else
    {
        return 0;
    }
}


/** changes the current active text */
void SdrTableObj::setActiveText( sal_Int32 nIndex )
{
    if( mpImpl.is() && mpImpl->mxTable.is() )
    {
        const sal_Int32 nColCount = mpImpl->mxTable->getColumnCount();
        if( nColCount )
        {
            CellPos aPos( nIndex % nColCount, nIndex / nColCount );
            if( isValid( aPos ) )
                setActiveCell( aPos );
        }
    }
}


/** returns the index of the text that contains the given point or -1 */
sal_Int32 SdrTableObj::CheckTextHit(const Point& rPnt) const
{
    if( mpImpl.is() && mpImpl->mxTable.is() )
    {
        CellPos aPos;
        if( CheckTableHit( rPnt, aPos.mnCol, aPos.mnRow ) == TableHitKind::CellTextArea )
            return aPos.mnRow * mpImpl->mxTable->getColumnCount() + aPos.mnCol;
    }

    return 0;
}

SdrOutliner* SdrTableObj::GetCellTextEditOutliner( const Cell& rCell ) const
{
    if( mpImpl.is() && (mpImpl->getCell( mpImpl->maEditPos ).get() == &rCell) )
        return mpEditingOutliner;
    else
        return nullptr;
}

const TableLayouter& SdrTableObj::getTableLayouter() const
{
    assert(mpImpl.is() && mpImpl->mpLayouter && "getTableLayouter() error: no mpImpl or mpLayouter (!)");
    return *(mpImpl->mpLayouter);
}

bool SdrTableObj::IsAutoGrowHeight() const
{
    return true;
}

bool SdrTableObj::IsAutoGrowWidth() const
{
    return true;
}

bool SdrTableObj::HasText() const
{
    return true;
}

bool SdrTableObj::IsTextEditActive( const CellPos& rPos )
{
    return mpEditingOutliner && mpImpl.is() && (rPos == mpImpl->maEditPos);
}


void SdrTableObj::onEditOutlinerStatusEvent( EditStatus* pEditStatus )
{
    if( (pEditStatus->GetStatusWord() & EditStatusFlags::TextHeightChanged) && mpImpl.is() && mpImpl->mpLayouter )
    {
        tools::Rectangle aRect0(getRectangle());
        setRectangle(maLogicRect);
        auto aRectangle = getRectangle();
        mpImpl->LayoutTable(aRectangle, false, false);
        setRectangle(aRectangle);
        SetBoundAndSnapRectsDirty();
        ActionChanged();
        BroadcastObjectChange();
        if (aRect0 != getRectangle())
            SendUserCall(SdrUserCallType::Resize,aRect0);
    }
}


void SdrTableObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const
{
    rInfo.bResizeFreeAllowed=true;
    rInfo.bResizePropAllowed=true;
    rInfo.bRotateFreeAllowed=false;
    rInfo.bRotate90Allowed  =false;
    rInfo.bMirrorFreeAllowed=false;
    rInfo.bMirror45Allowed  =false;
    rInfo.bMirror90Allowed  =false;

    // allow transparence
    rInfo.bTransparenceAllowed = true;

    rInfo.bShearAllowed     =false;
    rInfo.bEdgeRadiusAllowed=false;
    rInfo.bCanConvToPath    =false;
    rInfo.bCanConvToPoly    =false;
    rInfo.bCanConvToPathLineToArea=false;
    rInfo.bCanConvToPolyLineToArea=false;
    rInfo.bCanConvToContour = false;
}

SdrObjKind SdrTableObj::GetObjIdentifier() const
{
    return SdrObjKind::Table;
}

void SdrTableObj::TakeTextRect( SdrOutliner& rOutliner, tools::Rectangle& rTextRect, bool bNoEditText, tools::Rectangle* pAnchorRect, bool /*bLineWidth*/ ) const
{
    if( mpImpl.is() )
        TakeTextRect( mpImpl->maEditPos, rOutliner, rTextRect, bNoEditText, pAnchorRect );
}


void SdrTableObj::TakeTextRect( const CellPos& rPos, SdrOutliner& rOutliner, tools::Rectangle& rTextRect, bool bNoEditText, tools::Rectangle* pAnchorRect ) const
{
    if( !mpImpl.is())
        return;

    CellRef xCell( mpImpl->getCell( rPos ) );
    if( !xCell.is() )
        return;

    tools::Rectangle aAnkRect;
    TakeTextAnchorRect( rPos, aAnkRect );

    SdrTextVertAdjust eVAdj=xCell->GetTextVerticalAdjust();

    EEControlBits nStat0=rOutliner.GetControlWord();
    nStat0 |= EEControlBits::AUTOPAGESIZE;
    rOutliner.SetControlWord(nStat0);
    rOutliner.SetMinAutoPaperSize(Size());
    rOutliner.SetMaxAutoPaperSize(aAnkRect.GetSize());
    rOutliner.SetPaperSize(aAnkRect.GetSize());

    // #103516# New try with _BLOCK for hor and ver after completely
    // supporting full width for vertical text.
//  if( SDRTEXTHORZADJUST_BLOCK == eHAdj && !IsVerticalWriting())
//  {
    rOutliner.SetMinAutoPaperSize(Size(aAnkRect.GetWidth(), 0));
//  }
//  else if(SDRTEXTVERTADJUST_BLOCK == eVAdj && IsVerticalWriting())
//  {
//      rOutliner.SetMinAutoPaperSize(Size(0, aAnkRect.GetHeight()));
//  }


    // set text at outliner, maybe from edit outliner
    std::optional<OutlinerParaObject> pPara;
    if (xCell->GetOutlinerParaObject())
        pPara = *xCell->GetOutlinerParaObject();
    if (mpEditingOutliner && !bNoEditText && mpImpl->mxActiveCell == xCell )
        pPara = mpEditingOutliner->CreateParaObject();

    if (pPara)
    {
        const bool bHitTest(&getSdrModelFromSdrObject().GetHitTestOutliner() == &rOutliner);
        const SdrTextObj* pTestObj(rOutliner.GetTextObj());

        if( !pTestObj || !bHitTest || (pTestObj != this) || (pTestObj->GetOutlinerParaObject() != xCell->GetOutlinerParaObject()) )
        {
            if( bHitTest ) // #i33696# take back fix #i27510#
                rOutliner.SetTextObj( this );

            rOutliner.SetUpdateLayout(true);
            rOutliner.SetText(*pPara);
        }
    }
    else
    {
        rOutliner.SetTextObj( nullptr );
    }

    rOutliner.SetUpdateLayout(true);
    rOutliner.SetControlWord(nStat0);

    Point aTextPos(aAnkRect.TopLeft());
    Size aTextSiz(rOutliner.GetPaperSize());
    if (eVAdj==SDRTEXTVERTADJUST_CENTER || eVAdj==SDRTEXTVERTADJUST_BOTTOM)
    {
        tools::Long nFreeHgt=aAnkRect.GetHeight()-aTextSiz.Height();
        if (eVAdj==SDRTEXTVERTADJUST_CENTER)
            aTextPos.AdjustY(nFreeHgt/2 );
        if (eVAdj==SDRTEXTVERTADJUST_BOTTOM)
            aTextPos.AdjustY(nFreeHgt );
    }

    if (pAnchorRect)
        *pAnchorRect=aAnkRect;

    rTextRect=tools::Rectangle(aTextPos,aTextSiz);
}

const CellRef& SdrTableObj::getActiveCell() const
{
    if( mpImpl.is() )
    {
        if( !mpImpl->mxActiveCell.is() )
        {
            CellPos aPos;
            const_cast< SdrTableObj* >(this)->setActiveCell( aPos );
        }
        return mpImpl->mxActiveCell;
    }
    else
    {
        static CellRef xCell;
        return xCell;
    }
}


sal_Int32 SdrTableObj::getColumnCount() const
{
    return mpImpl.is() ? mpImpl->getColumnCount() : 0;
}

sal_Int32 SdrTableObj::getRowCount() const
{
    return mpImpl.is() ? mpImpl->getRowCount() : 0;
}

void SdrTableObj::changeEdge(bool bHorizontal, int nEdge, sal_Int32 nOffset)
{
    if (mpImpl.is())
        mpImpl->DragEdge(bHorizontal, nEdge, nOffset);
}

void SdrTableObj::setActiveCell( const CellPos& rPos )
{
    if( !(mpImpl.is() && mpImpl->mxTable.is()) )
        return;

    try
    {
        mpImpl->mxActiveCell = mpImpl->mxTable->getCell( rPos.mnCol, rPos.mnRow );
        if( mpImpl->mxActiveCell.is() && mpImpl->mxActiveCell->isMerged() )
        {
            CellPos aOrigin;
            findMergeOrigin( mpImpl->mxTable, rPos.mnCol, rPos.mnRow, aOrigin.mnCol, aOrigin.mnRow );
            mpImpl->mxActiveCell = mpImpl->mxTable->getCell( aOrigin.mnCol, aOrigin.mnRow );
            mpImpl->maEditPos = aOrigin;
        }
        else
        {
            mpImpl->maEditPos = rPos;
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION("svx.table", "");
    }
}


void SdrTableObj::getActiveCellPos( CellPos& rPos ) const
{
    rPos = mpImpl->maEditPos;
}


void SdrTableObj::getCellBounds( const CellPos& rPos, ::tools::Rectangle& rCellRect )
{
    if( mpImpl.is() )
    {
        CellRef xCell( mpImpl->getCell( rPos ) );
        if( xCell.is() )
            rCellRect = xCell->getCellRect();
    }
}


void SdrTableObj::TakeTextAnchorRect(tools::Rectangle& rAnchorRect) const
{
    if( mpImpl.is() )
        TakeTextAnchorRect( mpImpl->maEditPos, rAnchorRect );
}


void SdrTableObj::TakeTextAnchorRect( const CellPos& rPos, tools::Rectangle& rAnchorRect ) const
{
    tools::Rectangle aAnkRect(getRectangle());

    if( mpImpl.is() )
    {
        CellRef xCell( mpImpl->getCell( rPos ) );
        if( xCell.is() )
            xCell->TakeTextAnchorRect( aAnkRect );
    }

    ImpJustifyRect(aAnkRect);
    rAnchorRect=aAnkRect;
}


void SdrTableObj::TakeTextEditArea(Size* pPaperMin, Size* pPaperMax, tools::Rectangle* pViewInit, tools::Rectangle* pViewMin) const
{
    if( mpImpl.is() )
        TakeTextEditArea( mpImpl->maEditPos, pPaperMin, pPaperMax, pViewInit, pViewMin );
}


void SdrTableObj::TakeTextEditArea( const CellPos& rPos, Size* pPaperMin, Size* pPaperMax, tools::Rectangle* pViewInit, tools::Rectangle* pViewMin ) const
{
    Size aPaperMin,aPaperMax;
    tools::Rectangle aViewInit;
    TakeTextAnchorRect( rPos, aViewInit );

    Size aAnkSiz(aViewInit.GetSize());
    aAnkSiz.AdjustWidth( -1 ); aAnkSiz.AdjustHeight( -1 ); // because GetSize() increments by one

    Size aMaxSiz(aAnkSiz.Width(),1000000);
    Size aTmpSiz(getSdrModelFromSdrObject().GetMaxObjSize());
    if (aTmpSiz.Height()!=0)
        aMaxSiz.setHeight(aTmpSiz.Height() );

    CellRef xCell( mpImpl->getCell( rPos ) );
    SdrTextVertAdjust eVAdj = xCell.is() ? xCell->GetTextVerticalAdjust() : SDRTEXTVERTADJUST_TOP;

    aPaperMax=aMaxSiz;

    aPaperMin.setWidth( aAnkSiz.Width() );

    if (pViewMin!=nullptr)
    {
        *pViewMin=aViewInit;
        tools::Long nYFree=aAnkSiz.Height()-aPaperMin.Height();

        if (eVAdj==SDRTEXTVERTADJUST_TOP)
        {
            pViewMin->AdjustBottom( -nYFree );
        }
        else if (eVAdj==SDRTEXTVERTADJUST_BOTTOM)
        {
            pViewMin->AdjustTop(nYFree );
        }
        else
        {
            pViewMin->AdjustTop(nYFree/2 );
            pViewMin->SetBottom(pViewMin->Top()+aPaperMin.Height() );
        }
    }


    if(IsVerticalWriting())
        aPaperMin.setWidth( 0 );
    else
        aPaperMin.setHeight( 0 );

    if (pPaperMin!=nullptr) *pPaperMin=aPaperMin;
    if (pPaperMax!=nullptr) *pPaperMax=aPaperMax;
    if (pViewInit!=nullptr) *pViewInit=aViewInit;
}


EEAnchorMode SdrTableObj::GetOutlinerViewAnchorMode() const
{
    EEAnchorMode eRet=EEAnchorMode::TopLeft;
    CellRef xCell( getActiveCell() );
    if( xCell.is() )
    {
        SdrTextVertAdjust eV=xCell->GetTextVerticalAdjust();

        if (eV==SDRTEXTVERTADJUST_TOP)
        {
            eRet=EEAnchorMode::TopLeft;
        }
        else if (eV==SDRTEXTVERTADJUST_BOTTOM)
        {
            eRet=EEAnchorMode::BottomLeft;
        }
        else
        {
            eRet=EEAnchorMode::VCenterLeft;
        }
    }
    return eRet;
}


OUString SdrTableObj::TakeObjNameSingul() const
{
    OUString sName(SvxResId(STR_ObjNameSingulTable));

    OUString aName(GetName());
    if (!aName.isEmpty())
        sName += " '" + aName + "'";

    return sName;
}


OUString SdrTableObj::TakeObjNamePlural() const
{
    return SvxResId(STR_ObjNamePluralTable);
}


rtl::Reference<SdrObject> SdrTableObj::CloneSdrObject(SdrModel& rTargetModel) const
{
    return new SdrTableObj(rTargetModel, *this);
}


const tools::Rectangle& SdrTableObj::GetSnapRect() const
{
    return getRectangle();
}


void SdrTableObj::NbcSetSnapRect(const tools::Rectangle& rRect)
{
    NbcSetLogicRect( rRect );
}


const tools::Rectangle& SdrTableObj::GetLogicRect() const
{
    return maLogicRect;
}


void SdrTableObj::RecalcSnapRect()
{
}


bool SdrTableObj::BegTextEdit(SdrOutliner& rOutl)
{
    if( mpEditingOutliner != nullptr )
        return false;

    mpEditingOutliner=&rOutl;

    mbInEditMode = true;

    rOutl.Init( OutlinerMode::TextObject );
    rOutl.SetRefDevice(getSdrModelFromSdrObject().GetRefDevice());

    bool bUpdateMode = rOutl.SetUpdateLayout(false);
    Size aPaperMin;
    Size aPaperMax;
    tools::Rectangle aEditArea;
    TakeTextEditArea(&aPaperMin,&aPaperMax,&aEditArea,nullptr);

    rOutl.SetMinAutoPaperSize(aPaperMin);
    rOutl.SetMaxAutoPaperSize(aPaperMax);
    rOutl.SetPaperSize(aPaperMax);

    if (bUpdateMode) rOutl.SetUpdateLayout(true);

    EEControlBits nStat=rOutl.GetControlWord();
    nStat   |= EEControlBits::AUTOPAGESIZE;
    nStat   &=~EEControlBits::STRETCHING;
    rOutl.SetControlWord(nStat);

    OutlinerParaObject* pPara = GetOutlinerParaObject();
    if(pPara)
        rOutl.SetText(*pPara);

    rOutl.UpdateFields();
    rOutl.ClearModifyFlag();

    return true;
}


void SdrTableObj::EndTextEdit(SdrOutliner& rOutl)
{

    if (getSdrModelFromSdrObject().IsUndoEnabled() && !mpImpl->maUndos.empty())
    {
        // These actions should be on the undo stack after text edit.
        for (std::unique_ptr<SdrUndoAction>& pAction : mpImpl->maUndos)
            getSdrModelFromSdrObject().AddUndo( std::move(pAction));
        mpImpl->maUndos.clear();

        getSdrModelFromSdrObject().AddUndo(getSdrModelFromSdrObject().GetSdrUndoFactory().CreateUndoGeoObject(*this));
    }

    if(rOutl.IsModified())
    {
        std::optional<OutlinerParaObject> pNewText;
        Paragraph* p1stPara = rOutl.GetParagraph( 0 );
        sal_Int32 nParaCnt = rOutl.GetParagraphCount();

        if(p1stPara)
        {
            // to remove the grey field background
            rOutl.UpdateFields();

            // create new text object
            pNewText = rOutl.CreateParaObject( 0, nParaCnt );
        }
        SetOutlinerParaObject(std::move(pNewText));
    }

    mpEditingOutliner = nullptr;
    rOutl.Clear();
    EEControlBits nStat = rOutl.GetControlWord();
    nStat &= ~EEControlBits::AUTOPAGESIZE;
    rOutl.SetControlWord(nStat);

    mbInEditMode = false;
}


OutlinerParaObject* SdrTableObj::GetOutlinerParaObject() const
{
    CellRef xCell( getActiveCell() );
    if( xCell.is() )
        return xCell->GetOutlinerParaObject();
    else
        return nullptr;
}


void SdrTableObj::NbcSetOutlinerParaObject( std::optional<OutlinerParaObject> pTextObject, bool bAdjustTextFrameWidthAndHeight )
{
    CellRef xCell( getActiveCell() );
    if( !xCell.is() )
        return;

    // Update HitTestOutliner
    const SdrTextObj* pTestObj(getSdrModelFromSdrObject().GetHitTestOutliner().GetTextObj());

    if(pTestObj && pTestObj->GetOutlinerParaObject() == xCell->GetOutlinerParaObject())
    {
        getSdrModelFromSdrObject().GetHitTestOutliner().SetTextObj(nullptr);
    }

    xCell->SetOutlinerParaObject( std::move(pTextObject) );
    SetTextSizeDirty();
    if (bAdjustTextFrameWidthAndHeight)
        NbcAdjustTextFrameWidthAndHeight();
}


void SdrTableObj::NbcSetLogicRect(const tools::Rectangle& rRect, bool /*bAdaptTextMinSize*/)
{
    maLogicRect=rRect;
    ImpJustifyRect(maLogicRect);
    const bool bWidth = maLogicRect.getOpenWidth() != getRectangle().getOpenWidth();
    const bool bHeight = maLogicRect.getOpenHeight() != getRectangle().getOpenHeight();
    setRectangle(maLogicRect);
    if (mpImpl->mbSkipChangeLayout)
        // Avoid distributing newly available space between existing cells.
        NbcAdjustTextFrameWidthAndHeight();
    else
        NbcAdjustTextFrameWidthAndHeight(!bHeight, !bWidth);
    SetBoundAndSnapRectsDirty();
}


void SdrTableObj::AdjustToMaxRect( const tools::Rectangle& rMaxRect, bool /* bShrinkOnly = false */ )
{
    tools::Rectangle aAdjustRect( rMaxRect );
    aAdjustRect.setHeight( GetLogicRect().getOpenHeight() );
    SetLogicRect( aAdjustRect );
}


void SdrTableObj::NbcMove(const Size& rSiz)
{
    maLogicRect.Move(rSiz);
    SdrTextObj::NbcMove( rSiz );
    if( mpImpl.is() )
        mpImpl->UpdateCells(getRectangle());
}


void SdrTableObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact)
{
    tools::Rectangle aOldRect( maLogicRect );
    ResizeRect(maLogicRect,rRef,xFact,yFact);

    setRectangle(maLogicRect);
    NbcAdjustTextFrameWidthAndHeight( maLogicRect.GetHeight() == aOldRect.GetHeight(), maLogicRect.GetWidth() == aOldRect.GetWidth() );
    SetBoundAndSnapRectsDirty();
}


bool SdrTableObj::AdjustTextFrameWidthAndHeight()
{
    tools::Rectangle aNewRect(maLogicRect);
    bool bRet=AdjustTextFrameWidthAndHeight(aNewRect);
    if (bRet)
    {
        tools::Rectangle aBoundRect0;
        if (m_pUserCall!=nullptr)
            aBoundRect0=GetLastBoundRect();
        setRectangle(aNewRect);
        SetBoundAndSnapRectsDirty();
        SetChanged();
        BroadcastObjectChange();
        SendUserCall(SdrUserCallType::Resize,aBoundRect0);
    }
    return bRet;
}


bool SdrTableObj::AdjustTextFrameWidthAndHeight(tools::Rectangle& rR, bool bHeight, bool bWidth) const
{
    if(rR.IsEmpty() || !mpImpl.is() || !mpImpl->mxTable.is())
        return false;

    tools::Rectangle aRectangle( rR );
    mpImpl->LayoutTable( aRectangle, !bWidth, !bHeight );

    if( aRectangle != rR )
    {
        rR = aRectangle;
        return true;
    }
    else
    {
        return false;
    }
}


void SdrTableObj::NbcReformatText()
{
    NbcAdjustTextFrameWidthAndHeight();
}


bool SdrTableObj::IsVerticalWriting() const
{
    const SvxWritingModeItem& rModeItem = GetObjectItem( SDRATTR_TEXTDIRECTION );
    return rModeItem.GetValue() == css::text::WritingMode_TB_RL;
}


void SdrTableObj::SetVerticalWriting(bool bVertical)
{
    if(bVertical != IsVerticalWriting() )
    {
        SvxWritingModeItem aModeItem( css::text::WritingMode_LR_TB, SDRATTR_TEXTDIRECTION );
        SetObjectItem( aModeItem );
    }
}


WritingMode SdrTableObj::GetWritingMode() const
{
    SfxStyleSheet* pStyle = GetStyleSheet();
    if ( !pStyle )
        return WritingMode_LR_TB;

    WritingMode eWritingMode = WritingMode_LR_TB;
    const SfxItemSet &rSet = pStyle->GetItemSet();

    if ( const SvxWritingModeItem *pItem = rSet.GetItemIfSet( SDRATTR_TEXTDIRECTION ))
        eWritingMode = pItem->GetValue();

    if ( const SvxFrameDirectionItem *pItem;
        ( eWritingMode != WritingMode_TB_RL ) &&
         ( pItem = rSet.GetItemIfSet( EE_PARA_WRITINGDIR, false ) ) )
    {
        if ( pItem->GetValue() == SvxFrameDirection::Horizontal_LR_TB )
            eWritingMode = WritingMode_LR_TB;
        else
            eWritingMode = WritingMode_RL_TB;
    }

    return eWritingMode;
}

void SdrTableObj::AddUndo(SdrUndoAction* pUndo)
{
    mpImpl->maUndos.push_back(std::unique_ptr<SdrUndoAction>(pUndo));
}

void SdrTableObj::SetSkipChangeLayout(bool bSkipChangeLayout)
{
    mpImpl->mbSkipChangeLayout = bSkipChangeLayout;
}

bool SdrTableObj::IsReallyEdited() const
{
    return mpEditingOutliner && mpEditingOutliner->IsModified();
}

bool SdrTableObj::IsFontwork() const
{
    return false;
}

sal_uInt32 SdrTableObj::GetHdlCount() const
{
    sal_uInt32 nCount = SdrTextObj::GetHdlCount();
    const sal_Int32 nRowCount = mpImpl->getRowCount();
    const sal_Int32 nColCount = mpImpl->getColumnCount();

    if( nRowCount && nColCount )
        nCount += nRowCount + nColCount + 2 + 1;

    return nCount;
}

void SdrTableObj::AddToHdlList(SdrHdlList& rHdlList) const
{
    const sal_Int32 nRowCount = mpImpl->getRowCount();
    const sal_Int32 nColCount = mpImpl->getColumnCount();

    // first add row handles
    std::vector<TableEdgeHdl*> aRowEdges(nRowCount + 1);
    for (auto const & rEdge : mpImpl->mpLayouter->getHorizontalEdges())
    {
        Point aPoint(getRectangle().TopLeft());
        aPoint.AdjustY(rEdge.nPosition);

        std::unique_ptr<TableEdgeHdl> pHdl(new TableEdgeHdl(aPoint, true, rEdge.nMin, rEdge.nMax, nColCount + 1));
        pHdl->SetPointNum(rEdge.nIndex);
        aRowEdges[rEdge.nIndex] = pHdl.get();
        rHdlList.AddHdl(std::move(pHdl));
    }

    // second add column handles
    std::vector<TableEdgeHdl*> aColEdges(nColCount + 1);
    for (auto const & rEdge : mpImpl->mpLayouter->getVerticalEdges())
    {
        Point aPoint(getRectangle().TopLeft());
        aPoint.AdjustX(rEdge.nPosition);

        std::unique_ptr<TableEdgeHdl> pHdl(new TableEdgeHdl(aPoint, false, rEdge.nMin, rEdge.nMax, nRowCount + 1));
        pHdl->SetPointNum(rEdge.nIndex);
        aColEdges[rEdge.nIndex] = pHdl.get();
        rHdlList.AddHdl(std::move(pHdl));
    }

    // now add visible edges to row and column handles
    if( mpImpl->mpLayouter )
    {
        TableLayouter& rLayouter = *mpImpl->mpLayouter;

        sal_Int32 nY = 0;

        for( sal_Int32 nRow = 0; nRow <= nRowCount; ++nRow )
        {
            const sal_Int32 nRowHeight = (nRow == nRowCount) ? 0 : rLayouter.getRowHeight(nRow);
            sal_Int32 nX = 0;

            for( sal_Int32 nCol = 0; nCol <= nColCount; ++nCol )
            {
                const sal_Int32 nColWidth = (nCol == nColCount) ? 0 : rLayouter.getColumnWidth(nCol);

                if( nRowHeight > 0 )
                {
                    if( rLayouter.isEdgeVisible( nCol, nRow, false ) )
                        aColEdges[nCol]->SetEdge( nRow, nY, nY + nRowHeight, (rLayouter.getBorderLine( nCol, nRow, false ) == nullptr) ? Visible : Invisible);
                }

                if( nColWidth > 0 )
                {
                    if( rLayouter.isEdgeVisible( nCol, nRow, true ) )
                        aRowEdges[nRow]->SetEdge( nCol, nX, nX + nColWidth, (rLayouter.getBorderLine( nCol, nRow, true ) == nullptr) ? Visible : Invisible);
                }

                nX += nColWidth;
            }

            nY += nRowHeight;
        }
    }

    // add remaining handles
    SdrHdlList tempList(nullptr);
    auto aRectangle = getRectangle();
    tempList.AddHdl( std::make_unique<TableBorderHdl>(aRectangle, !IsTextEditActive() ) );
    tempList.AddHdl( std::make_unique<SdrHdl>(aRectangle.TopLeft(),SdrHdlKind::UpperLeft) );
    tempList.AddHdl( std::make_unique<SdrHdl>(aRectangle.TopCenter(),SdrHdlKind::Upper) );
    tempList.AddHdl( std::make_unique<SdrHdl>(aRectangle.TopRight(),SdrHdlKind::UpperRight) );
    tempList.AddHdl( std::make_unique<SdrHdl>(aRectangle.LeftCenter(),SdrHdlKind::Left) );
    tempList.AddHdl( std::make_unique<SdrHdl>(aRectangle.RightCenter(),SdrHdlKind::Right) );
    tempList.AddHdl( std::make_unique<SdrHdl>(aRectangle.BottomLeft(),SdrHdlKind::LowerLeft) );
    tempList.AddHdl( std::make_unique<SdrHdl>(aRectangle.BottomCenter(),SdrHdlKind::Lower) );
    tempList.AddHdl( std::make_unique<SdrHdl>(aRectangle.BottomRight(),SdrHdlKind::LowerRight) );
    for( size_t nHdl = 0; nHdl < tempList.GetHdlCount(); ++nHdl )
        tempList.GetHdl(nHdl)->SetMoveOutside(true);
    tempList.MoveTo(rHdlList);

    const size_t nHdlCount = rHdlList.GetHdlCount();
    for( size_t nHdl = 0; nHdl < nHdlCount; ++nHdl )
        rHdlList.GetHdl(nHdl)->SetObj(const_cast<SdrTableObj*>(this));
}

// Dragging

bool SdrTableObj::hasSpecialDrag() const
{
    return true;
}

bool SdrTableObj::beginSpecialDrag(SdrDragStat& rDrag) const
{
    const SdrHdl* pHdl = rDrag.GetHdl();
    const SdrHdlKind eHdl((pHdl == nullptr) ? SdrHdlKind::Move : pHdl->GetKind());

    switch( eHdl )
    {
        case SdrHdlKind::UpperLeft:
        case SdrHdlKind::Upper:
        case SdrHdlKind::UpperRight:
        case SdrHdlKind::Left:
        case SdrHdlKind::Right:
        case SdrHdlKind::LowerLeft:
        case SdrHdlKind::Lower:
        case SdrHdlKind::LowerRight:
        case SdrHdlKind::Move:
        {
            break;
        }

        case SdrHdlKind::User:
        {
            rDrag.SetEndDragChangesAttributes(false);
            rDrag.SetNoSnap();
            break;
        }

        default:
        {
            return false;
        }
    }

    return true;
}

bool SdrTableObj::applySpecialDrag(SdrDragStat& rDrag)
{
    bool bRet(true);
    const SdrHdl* pHdl = rDrag.GetHdl();
    const SdrHdlKind eHdl((pHdl == nullptr) ? SdrHdlKind::Move : pHdl->GetKind());

    switch( eHdl )
    {
        case SdrHdlKind::UpperLeft:
        case SdrHdlKind::Upper:
        case SdrHdlKind::UpperRight:
        case SdrHdlKind::Left:
        case SdrHdlKind::Right:
        case SdrHdlKind::LowerLeft:
        case SdrHdlKind::Lower:
        case SdrHdlKind::LowerRight:
        {
            const tools::Rectangle aNewRectangle(ImpDragCalcRect(rDrag));

            if (aNewRectangle != getRectangle())
            {
                   NbcSetLogicRect(aNewRectangle);
            }

            break;
        }

        case SdrHdlKind::Move:
        {
            NbcMove( Size( rDrag.GetDX(), rDrag.GetDY() ) );
            break;
        }

        case SdrHdlKind::User:
        {
            rDrag.SetEndDragChangesAttributes(false);
            rDrag.SetNoSnap();
            const TableEdgeHdl* pEdgeHdl = dynamic_cast< const TableEdgeHdl* >( pHdl );

            if( pEdgeHdl )
            {
                if( IsInserted() )
                {
                    rDrag.SetEndDragChangesAttributes(true);
                    rDrag.SetEndDragChangesLayout(true);
                }

                mpImpl->DragEdge( pEdgeHdl->IsHorizontalEdge(), pEdgeHdl->GetPointNum(), pEdgeHdl->GetValidDragOffset( rDrag ) );
            }
            break;
        }

        default:
        {
            bRet = false;
        }
    }

    return bRet;
}

basegfx::B2DPolyPolygon SdrTableObj::getSpecialDragPoly(const SdrDragStat& rDrag) const
{
    basegfx::B2DPolyPolygon aRetval;
    const SdrHdl* pHdl = rDrag.GetHdl();

    if( pHdl && (SdrHdlKind::User == pHdl->GetKind()) )
    {
        const TableEdgeHdl* pEdgeHdl = dynamic_cast< const TableEdgeHdl* >( pHdl );

        if( pEdgeHdl )
        {
            aRetval = pEdgeHdl->getSpecialDragPoly( rDrag );
        }
    }

    return aRetval;
}


// Create


bool SdrTableObj::BegCreate(SdrDragStat& rStat)
{
    rStat.SetOrtho4Possible();
    tools::Rectangle aRect1(rStat.GetStart(), rStat.GetNow());
    aRect1.Normalize();
    rStat.SetActionRect(aRect1);
    setRectangle(aRect1);
    return true;
}


bool SdrTableObj::MovCreate(SdrDragStat& rStat)
{
    tools::Rectangle aRect1;
    rStat.TakeCreateRect(aRect1);
    ImpJustifyRect(aRect1);
    rStat.SetActionRect(aRect1);
    setRectangle(aRect1); // for ObjName
    SetBoundRectDirty();
    m_bSnapRectDirty=true;
    return true;
}


bool SdrTableObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
{
    auto aRectangle = getRectangle();
    rStat.TakeCreateRect(aRectangle);
    ImpJustifyRect(aRectangle);
    setRectangle(aRectangle);
    return (eCmd==SdrCreateCmd::ForceEnd || rStat.GetPointCount()>=2);
}

void SdrTableObj::BrkCreate(SdrDragStat& /*rStat*/)
{
}


bool SdrTableObj::BckCreate(SdrDragStat& /*rStat*/)
{
    return true;
}


basegfx::B2DPolyPolygon SdrTableObj::TakeCreatePoly(const SdrDragStat& rDrag) const
{
    tools::Rectangle aRect1;
    rDrag.TakeCreateRect(aRect1);
    aRect1.Normalize();

    basegfx::B2DPolyPolygon aRetval;
    const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(aRect1);
    aRetval.append(basegfx::utils::createPolygonFromRect(aRange));
    return aRetval;
}


PointerStyle SdrTableObj::GetCreatePointer() const
{
    return PointerStyle::Cross;
}


void SdrTableObj::createCell( CellRef& xNewCell )
{
    xNewCell = Cell::create( *this );
}


std::unique_ptr<SdrObjGeoData> SdrTableObj::NewGeoData() const
{
    return std::make_unique<TableObjectGeoData>();
}


void SdrTableObj::SaveGeoData(SdrObjGeoData& rGeo) const
{
    DBG_ASSERT( dynamic_cast< TableObjectGeoData* >( &rGeo ), "svx::SdrTableObj::SaveGeoData(), illegal geo data!" );
    SdrTextObj::SaveGeoData (rGeo);

    static_cast<TableObjectGeoData &>(rGeo).maLogicRect = maLogicRect;
}


void SdrTableObj::RestoreGeoData(const SdrObjGeoData& rGeo)
{
    DBG_ASSERT( dynamic_cast< const TableObjectGeoData* >( &rGeo ), "svx::SdrTableObj::RestoreGeoData(), illegal geo data!" );

    maLogicRect = static_cast<const TableObjectGeoData &>(rGeo).maLogicRect;

    SdrTextObj::RestoreGeoData (rGeo);

    if( mpImpl.is() )
    {
        auto aRectangle = getRectangle();
        mpImpl->LayoutTable(aRectangle, false, false);
        setRectangle(aRectangle);
    }
    ActionChanged();
}

void SdrTableObj::CropTableModelToSelection(const CellPos& rStart, const CellPos& rEnd)
{
    if(!mpImpl.is())
    {
        return;
    }

    mpImpl->CropTableModelToSelection(rStart, rEnd);
}

void SdrTableObj::LayoutTableHeight(tools::Rectangle& rArea)
{
    if( mpImpl.is() && mpImpl->mpLayouter)
    {
        mpImpl->mpLayouter->LayoutTableHeight(rArea, /*bFit*/false);
    }
}

void SdrTableObj::DistributeColumns( sal_Int32 nFirstColumn, sal_Int32 nLastColumn, const bool bOptimize, const bool bMinimize )
{
    if( mpImpl.is() && mpImpl->mpLayouter )
    {
        TableModelNotifyGuard aGuard( mpImpl->mxTable.get() );
        auto aRectangle = getRectangle();
        mpImpl->mpLayouter->DistributeColumns(aRectangle, nFirstColumn, nLastColumn, bOptimize, bMinimize);
        setRectangle(aRectangle);
    }
}


void SdrTableObj::DistributeRows( sal_Int32 nFirstRow, sal_Int32 nLastRow, const bool bOptimize, const bool bMinimize )
{
    if( mpImpl.is() && mpImpl->mpLayouter )
    {
        TableModelNotifyGuard aGuard( mpImpl->mxTable.get() );
        auto aRectangle = getRectangle();
        mpImpl->mpLayouter->DistributeRows(aRectangle, nFirstRow, nLastRow, bOptimize, bMinimize);
        setRectangle(aRectangle);
    }
}


void SdrTableObj::SetChanged()
{
    if( mpImpl.is() )
    {
        auto aRectangle = getRectangle();
        mpImpl->LayoutTable(aRectangle, false, false);
        setRectangle(aRectangle);
    }

    ::SdrTextObj::SetChanged();
}


void SdrTableObj::uno_lock()
{
    if( mpImpl.is() && mpImpl->mxTable.is() )
        mpImpl->mxTable->lockBroadcasts();
}


void SdrTableObj::uno_unlock()
{
    if( mpImpl.is() && mpImpl->mxTable.is() )
        mpImpl->mxTable->unlockBroadcasts();
}

void SdrTableObj::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrTableObj"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);

    SdrObject::dumpAsXml(pWriter);

    mpImpl->dumpAsXml(pWriter);

    (void)xmlTextWriterEndElement(pWriter);
}

bool SdrTableObj::createTableEdgesJson(boost::property_tree::ptree & rJsonRoot)
{
    if (!mpImpl.is() || !mpImpl->mxTable.is())
        return false;

    tools::Rectangle aRect = GetCurrentBoundRect();
    boost::property_tree::ptree aTableColumns;
    {
        aTableColumns.put("tableOffset", o3tl::toTwips(aRect.Left(), o3tl::Length::mm100));

        boost::property_tree::ptree aEntries;
        auto const aEdges = mpImpl->mpLayouter->getVerticalEdges();
        for (auto & rEdge : aEdges)
        {
            if (rEdge.nIndex == 0)
            {
                aTableColumns.put("left", o3tl::toTwips(rEdge.nPosition, o3tl::Length::mm100));
            }
            else if (rEdge.nIndex == sal_Int32(aEdges.size() - 1))
            {
                aTableColumns.put("right", o3tl::toTwips(rEdge.nPosition, o3tl::Length::mm100));
            }
            else
            {
                boost::property_tree::ptree aEntry;
                aEntry.put("position", o3tl::toTwips(rEdge.nPosition, o3tl::Length::mm100));
                aEntry.put("min", o3tl::toTwips(rEdge.nPosition + rEdge.nMin, o3tl::Length::mm100));
                aEntry.put("max", o3tl::toTwips(rEdge.nPosition + rEdge.nMax, o3tl::Length::mm100));
                aEntry.put("hidden", false);
                aEntries.push_back(std::make_pair("", aEntry));
            }
        }
        aTableColumns.push_back(std::make_pair("entries", aEntries));
    }
    rJsonRoot.add_child("columns", aTableColumns);

    boost::property_tree::ptree aTableRows;
    {
        aTableRows.put("tableOffset", o3tl::toTwips(aRect.Top(), o3tl::Length::mm100));

        boost::property_tree::ptree aEntries;
        auto const aEdges = mpImpl->mpLayouter->getHorizontalEdges();
        for (auto & rEdge : aEdges)
        {
            if (rEdge.nIndex == 0)
            {
                aTableRows.put("left", o3tl::toTwips(rEdge.nPosition, o3tl::Length::mm100));
            }
            else if (rEdge.nIndex == sal_Int32(aEdges.size() - 1))
            {
                aTableRows.put("right", o3tl::toTwips(rEdge.nPosition, o3tl::Length::mm100));
            }
            else
            {
                boost::property_tree::ptree aEntry;
                aEntry.put("position", o3tl::toTwips(rEdge.nPosition, o3tl::Length::mm100));
                aEntry.put("min", o3tl::toTwips(rEdge.nPosition + rEdge.nMin, o3tl::Length::mm100));
                aEntry.put("max", o3tl::toTwips(rEdge.nPosition + rEdge.nMax, o3tl::Length::mm100));
                aEntry.put("hidden", false);
                aEntries.push_back(std::make_pair("", aEntry));
            }
        }
        aTableRows.push_back(std::make_pair("entries", aEntries));
    }
    rJsonRoot.add_child("rows", aTableRows);
    return true;
}

}

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