/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_svx.hxx"

#include <com/sun/star/table/XMergeableCell.hpp>

#include <algorithm>
#include <boost/bind.hpp>

#include <vcl/svapp.hxx>
#include <osl/mutex.hxx>

#include "cell.hxx"
#include "cellcursor.hxx"
#include "tablemodel.hxx"
#include "tablerow.hxx"
#include "tablerows.hxx"
#include "tablecolumn.hxx"
#include "tablecolumns.hxx"
#include "tableundo.hxx"
#include "svx/svdotable.hxx"
#include "svx/svdmodel.hxx"
#include "svdstr.hrc"
#include "svdglob.hxx"

//#define PLEASE_DEBUG_THE_TABLES 1

using ::rtl::OUString;
using namespace ::osl;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::table;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::util;

// -----------------------------------------------------------------------------

namespace sdr { namespace table {

// -----------------------------------------------------------------------------

// removes the given range from a vector
template< class Vec, class Iter > void remove_range( Vec& rVector, sal_Int32 nIndex, sal_Int32 nCount )
{
    const sal_Int32 nSize = static_cast<sal_Int32>(rVector.size());
    if( nCount && (nIndex >= 0) && (nIndex < nSize) )
    {
        if( (nIndex + nCount) >= nSize )
        {
            // remove at end
            rVector.resize( nIndex );
        }
        else
        {
            Iter aBegin( rVector.begin() );
            while( nIndex-- )
                aBegin++;
            if( nCount == 1 )
            {
                rVector.erase( aBegin );
            }
            else
            {
                Iter aEnd( aBegin );
        
                while( nCount-- )
                    aEnd++;
                rVector.erase( aBegin, aEnd );
            }
        }
    }
}

// -----------------------------------------------------------------------------

/** inserts a range into a vector */
template< class Vec, class Iter, class Entry > sal_Int32 insert_range( Vec& rVector, sal_Int32 nIndex, sal_Int32 nCount )
{
    if( nCount )
    {
        if( nIndex >= static_cast< sal_Int32 >( rVector.size() ) )
        {
            // append at end
            nIndex = static_cast< sal_Int32 >( rVector.size() ); // cap to end
            rVector.resize( nIndex + nCount );
        }
        else
        {
            // insert
            sal_Int32 nFind = nIndex;
            Iter aIter( rVector.begin() );
            while( nFind-- )
                aIter++;

            Entry aEmpty;
            rVector.insert( aIter, nCount, aEmpty );
        }
    }
    return nIndex;
}

// -----------------------------------------------------------------------------

TableModel::TableModel( SdrTableObj* pTableObj )
: TableModelBase( m_aMutex  )
, mpTableObj( pTableObj )
, mbModified( sal_False )
, mbNotifyPending( false )
, mnNotifyLock( 0 )
{
}

TableModel::TableModel( SdrTableObj* pTableObj, const TableModelRef& xSourceTable )
: TableModelBase( m_aMutex  )
, mpTableObj( pTableObj )
, mbModified( sal_False )
, mbNotifyPending( false )
, mnNotifyLock( 0 )
{
    if( xSourceTable.is() )
    {
        const sal_Int32 nColCount = xSourceTable->getColumnCountImpl();
        const sal_Int32 nRowCount = xSourceTable->getRowCountImpl();

        init( nColCount, nRowCount );

        sal_Int32 nRows = nRowCount;
        while( nRows-- )
            (*maRows[nRows]) = (*xSourceTable->maRows[nRows]);

        sal_Int32 nColumns = nColCount;
        while( nColumns-- )
            (*maColumns[nColumns]) = (*xSourceTable->maColumns[nColumns]);

        // copy cells
        for( sal_Int32 nCol = 0; nCol < nColCount; ++nCol )
        {
            for( sal_Int32 nRow = 0; nRow < nRowCount; ++nRow )
            {
                CellRef xTargetCell( getCell( nCol, nRow ) );
                if( xTargetCell.is() )
                    xTargetCell->cloneFrom( xSourceTable->getCell( nCol, nRow ) );
            }
        }
    }
}

// -----------------------------------------------------------------------------

TableModel::~TableModel()
{
}

// -----------------------------------------------------------------------------

void TableModel::init( sal_Int32 nColumns, sal_Int32 nRows )
{
    if( nRows < 20 )
        maRows.reserve( 20 );

    if( nColumns < 20 )
        maColumns.reserve( 20 );

    if( nRows && nColumns )
    {
        maColumns.resize( nColumns );
        maRows.resize( nRows );

        while( nRows-- )
            maRows[nRows].set( new TableRow( this, nRows, nColumns ) );

        while( nColumns-- )
            maColumns[nColumns].set( new TableColumn( this, nColumns ) );
    }
}

// -----------------------------------------------------------------------------
// ICellRange
// -----------------------------------------------------------------------------

sal_Int32 TableModel::getLeft()
{
    return 0;
}

// -----------------------------------------------------------------------------

sal_Int32 TableModel::getTop()
{
    return 0;
}

// -----------------------------------------------------------------------------

sal_Int32 TableModel::getRight()
{
    return getColumnCount();
}

// -----------------------------------------------------------------------------

sal_Int32 TableModel::getBottom()
{
    return getRowCount();
}

// -----------------------------------------------------------------------------

Reference< XTable > TableModel::getTable()
{
    return this;
}

// -----------------------------------------------------------------------------

void TableModel::UndoInsertRows( sal_Int32 nIndex, sal_Int32 nCount )
{
    TableModelNotifyGuard aGuard( this );

    // remove the rows
    remove_range<RowVector,RowVector::iterator>( maRows, nIndex, nCount );
    updateRows();
    setModified(sal_True);
}

// -----------------------------------------------------------------------------

void TableModel::UndoRemoveRows( sal_Int32 nIndex, RowVector& aRows )
{
    TableModelNotifyGuard aGuard( this );

    const sal_Int32 nCount = sal::static_int_cast< sal_Int32 >( aRows.size() );

    nIndex = insert_range<RowVector,RowVector::iterator,TableRowRef>( maRows, nIndex, nCount );

    for( sal_Int32 nOffset = 0; nOffset < nCount; ++nOffset )
        maRows[nIndex+nOffset] = aRows[nOffset];

    updateRows();
    setModified(sal_True);
}

// -----------------------------------------------------------------------------

void TableModel::UndoInsertColumns( sal_Int32 nIndex, sal_Int32 nCount )
{
    TableModelNotifyGuard aGuard( this );

    // now remove the columns
    remove_range<ColumnVector,ColumnVector::iterator>( maColumns, nIndex, nCount );
    sal_Int32 nRows = getRowCountImpl();
    while( nRows-- )
        maRows[nRows]->removeColumns( nIndex, nCount );

    updateColumns();
    setModified(sal_True);
}

// -----------------------------------------------------------------------------

void TableModel::UndoRemoveColumns( sal_Int32 nIndex, ColumnVector& aCols, CellVector& aCells )
{
    TableModelNotifyGuard aGuard( this );

    const sal_Int32 nCount = sal::static_int_cast< sal_Int32 >( aCols.size() );

    // assert if there are not enough cells saved
    DBG_ASSERT( (aCols.size() * maRows.size()) == aCells.size(), "sdr::table::TableModel::UndoRemoveColumns(), invalid undo data!" );

    nIndex = insert_range<ColumnVector,ColumnVector::iterator,TableColumnRef>( maColumns, nIndex, nCount );
    for( sal_Int32 nOffset = 0; nOffset < nCount; ++nOffset )
        maColumns[nIndex+nOffset] = aCols[nOffset];

    CellVector::iterator aIter( aCells.begin() );

    sal_Int32 nRows = getRowCountImpl();
    for( sal_Int32 nRow = 0; nRow < nRows; ++nRow )
        maRows[nRow]->insertColumns( nIndex, nCount, &aIter );

    updateColumns();
    setModified(sal_True);
}

// -----------------------------------------------------------------------------
// XTable
// -----------------------------------------------------------------------------

Reference< XCellCursor > SAL_CALL TableModel::createCursor() throw (RuntimeException)
{
    ::SolarMutexGuard aGuard;
    return createCursorByRange( Reference< XCellRange >( this ) );
}

// -----------------------------------------------------------------------------

Reference< XCellCursor > SAL_CALL TableModel::createCursorByRange( const Reference< XCellRange >& Range ) throw (IllegalArgumentException, RuntimeException)
{
    ::SolarMutexGuard aGuard;

    ICellRange* pRange = dynamic_cast< ICellRange* >( Range.get() );
    if( (pRange == 0) || (pRange->getTable().get() != this) )
        throw IllegalArgumentException();

    TableModelRef xModel( this );
    return new CellCursor( xModel, pRange->getLeft(), pRange->getTop(), pRange->getRight(), pRange->getBottom() );
}

// -----------------------------------------------------------------------------

sal_Int32 SAL_CALL TableModel::getRowCount() throw (RuntimeException)
{
    ::SolarMutexGuard aGuard;
    return getRowCountImpl();
}

// -----------------------------------------------------------------------------

sal_Int32 SAL_CALL TableModel::getColumnCount() throw (RuntimeException)
{
    ::SolarMutexGuard aGuard;
    return getColumnCountImpl();
}

// -----------------------------------------------------------------------------
// XComponent
// -----------------------------------------------------------------------------

void TableModel::dispose() throw (RuntimeException)
{
    ::SolarMutexGuard aGuard;
    TableModelBase::dispose();
}

// -----------------------------------------------------------------------------

void SAL_CALL TableModel::addEventListener( const Reference< XEventListener >& xListener ) throw (RuntimeException)
{
    TableModelBase::addEventListener( xListener );
}

// -----------------------------------------------------------------------------

void SAL_CALL TableModel::removeEventListener( const Reference< XEventListener >& xListener ) throw (RuntimeException)
{
    TableModelBase::removeEventListener( xListener );
}

// -----------------------------------------------------------------------------
// XModifiable
// -----------------------------------------------------------------------------

sal_Bool SAL_CALL TableModel::isModified(  ) throw (RuntimeException)
{
    ::SolarMutexGuard aGuard;
    return mbModified;
}

// -----------------------------------------------------------------------------

void SAL_CALL TableModel::setModified( sal_Bool bModified ) throw (PropertyVetoException, RuntimeException)
{
    {
        ::SolarMutexGuard aGuard;
        mbModified = bModified;
    }
    if( bModified )
        notifyModification();
}

// -----------------------------------------------------------------------------
// XModifyBroadcaster
// -----------------------------------------------------------------------------

void SAL_CALL TableModel::addModifyListener( const Reference< XModifyListener >& xListener ) throw (RuntimeException)
{
    rBHelper.addListener( XModifyListener::static_type() , xListener );
}

// -----------------------------------------------------------------------------

void SAL_CALL TableModel::removeModifyListener( const Reference< XModifyListener >& xListener ) throw (RuntimeException)
{
    rBHelper.removeListener( XModifyListener::static_type() , xListener );
}

// -----------------------------------------------------------------------------
// XColumnRowRange
// -----------------------------------------------------------------------------

Reference< XTableColumns > SAL_CALL TableModel::getColumns() throw (RuntimeException)
{
    ::SolarMutexGuard aGuard;

    if( !mxTableColumns.is() )
        mxTableColumns.set( new TableColumns( this ) );
    return mxTableColumns.get();
}

// -----------------------------------------------------------------------------

Reference< XTableRows > SAL_CALL TableModel::getRows() throw (RuntimeException)
{
    ::SolarMutexGuard aGuard;

    if( !mxTableRows.is() )
        mxTableRows.set( new TableRows( this ) );
    return mxTableRows.get();
}

// -----------------------------------------------------------------------------
// XCellRange
// -----------------------------------------------------------------------------

Reference< XCell > SAL_CALL TableModel::getCellByPosition( sal_Int32 nColumn, sal_Int32 nRow ) throw ( IndexOutOfBoundsException, RuntimeException)
{
    ::SolarMutexGuard aGuard;

    CellRef xCell( getCell( nColumn, nRow ) );
    if( xCell.is() )
        return xCell.get();

    throw IndexOutOfBoundsException();
}

// -----------------------------------------------------------------------------

Reference< XCellRange > SAL_CALL TableModel::getCellRangeByPosition( sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ) throw (IndexOutOfBoundsException, RuntimeException)
{
    ::SolarMutexGuard aGuard;

    if( (nLeft >= 0) && (nTop >= 0) && (nRight >= nLeft) && (nBottom >= nTop) && (nRight < getColumnCountImpl()) && (nBottom < getRowCountImpl() ) )
    {
        TableModelRef xModel( this );
        return new CellRange( xModel, nLeft, nTop, nRight, nBottom );
    }

    throw IndexOutOfBoundsException();
}

// -----------------------------------------------------------------------------

Reference< XCellRange > SAL_CALL TableModel::getCellRangeByName( const OUString& /*aRange*/ ) throw (RuntimeException)
{
    return Reference< XCellRange >();
}

// -----------------------------------------------------------------------------
// XPropertySet
// -----------------------------------------------------------------------------

Reference< XPropertySetInfo > SAL_CALL TableModel::getPropertySetInfo(  ) throw (RuntimeException)
{
    Reference< XPropertySetInfo > xInfo;
    return xInfo;
}

// -----------------------------------------------------------------------------

void SAL_CALL TableModel::setPropertyValue( const ::rtl::OUString& /*aPropertyName*/, const Any& /*aValue*/ ) throw (UnknownPropertyException, PropertyVetoException, IllegalArgumentException, WrappedTargetException, RuntimeException)
{
}

// -----------------------------------------------------------------------------

Any SAL_CALL TableModel::getPropertyValue( const OUString& /*PropertyName*/ ) throw (UnknownPropertyException, WrappedTargetException, RuntimeException)
{
    return Any();
}

// -----------------------------------------------------------------------------

void SAL_CALL TableModel::addPropertyChangeListener( const OUString& /*aPropertyName*/, const Reference< XPropertyChangeListener >& /*xListener*/ ) throw (UnknownPropertyException, WrappedTargetException, RuntimeException)
{
}

// -----------------------------------------------------------------------------

void SAL_CALL TableModel::removePropertyChangeListener( const OUString& /*aPropertyName*/, const Reference< XPropertyChangeListener >& /*xListener*/ ) throw (UnknownPropertyException, WrappedTargetException, RuntimeException)
{
}

// -----------------------------------------------------------------------------

void SAL_CALL TableModel::addVetoableChangeListener( const OUString& /*aPropertyName*/, const Reference< XVetoableChangeListener >& /*xListener*/ ) throw (UnknownPropertyException, WrappedTargetException, RuntimeException)
{
}

// -----------------------------------------------------------------------------

void SAL_CALL TableModel::removeVetoableChangeListener( const OUString& /*aPropertyName*/, const Reference< XVetoableChangeListener >& /*xListener*/ ) throw (UnknownPropertyException, WrappedTargetException, RuntimeException)
{
}

// -----------------------------------------------------------------------------
// XFastPropertySet
// -----------------------------------------------------------------------------

void SAL_CALL TableModel::setFastPropertyValue( ::sal_Int32 /*nHandle*/, const Any& /*aValue*/ ) throw (UnknownPropertyException, PropertyVetoException, IllegalArgumentException, WrappedTargetException, RuntimeException)
{
}

// -----------------------------------------------------------------------------

Any SAL_CALL TableModel::getFastPropertyValue( ::sal_Int32 /*nHandle*/ ) throw (UnknownPropertyException, WrappedTargetException, RuntimeException)
{
    Any aAny;
    return aAny;
}

// -----------------------------------------------------------------------------
// internals
// -----------------------------------------------------------------------------

sal_Int32 TableModel::getRowCountImpl() const
{
    return static_cast< sal_Int32 >( maRows.size() );
}

// -----------------------------------------------------------------------------

sal_Int32 TableModel::getColumnCountImpl() const
{
    return static_cast< sal_Int32 >( maColumns.size() );
}

// -----------------------------------------------------------------------------

void TableModel::disposing()
{
    if( !maRows.empty() )
    {
        RowVector::iterator aIter( maRows.begin() );
        while( aIter != maRows.end() )
            (*aIter++)->dispose();
        RowVector().swap(maRows);
    }

    if( !maColumns.empty() )
    {
        ColumnVector::iterator aIter( maColumns.begin() );
        while( aIter != maColumns.end() )
            (*aIter++)->dispose();
        ColumnVector().swap(maColumns);
    }

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

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

    mpTableObj = 0;
}

// -----------------------------------------------------------------------------
// XBroadcaster
// -----------------------------------------------------------------------------

void TableModel::lockBroadcasts() throw (RuntimeException)
{
    ::SolarMutexGuard aGuard;
    ++mnNotifyLock;
}
// -----------------------------------------------------------------------------

void TableModel::unlockBroadcasts() throw (RuntimeException)
{
    ::SolarMutexGuard aGuard;
    --mnNotifyLock;
    if( mnNotifyLock <= 0 )
    {
        mnNotifyLock = 0;
        if( mbNotifyPending )
            notifyModification();
    }
}

// -----------------------------------------------------------------------------
#ifdef PLEASE_DEBUG_THE_TABLES
#include <stdio.h>
#endif

void TableModel::notifyModification()
{
    ::osl::MutexGuard guard( m_aMutex );
    if( (mnNotifyLock == 0) && mpTableObj && mpTableObj->GetModel() )
    {
        mbNotifyPending = false;

        ::cppu::OInterfaceContainerHelper * pModifyListeners = rBHelper.getContainer( XModifyListener::static_type() );
        if( pModifyListeners )
        {
            EventObject aSource;
            aSource.Source = static_cast< ::cppu::OWeakObject* >(this);
            pModifyListeners->notifyEach( &XModifyListener::modified, aSource);
        }
    }
    else
    {
        mbNotifyPending = true;
    }

#ifdef PLEASE_DEBUG_THE_TABLES
        FILE* file = fopen( "c:\\table.xml","w" );

        const sal_Int32 nColCount = getColumnCountImpl();
        const sal_Int32 nRowCount = getRowCountImpl();

        fprintf( file, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\r" );
        fprintf( file, "<table columns=\"%ld\" rows=\"%ld\" updated=\"%s\">\n\r", nColCount, nRowCount, mbNotifyPending ? "false" : "true");

        for( sal_Int32 nCol = 0; nCol < nColCount; ++nCol )
        {
            fprintf( file, "<column this=\"%lx\"/>\n\r", maColumns[nCol].get() );
        }

        // first check merged cells before and inside the removed rows
        for( sal_Int32 nRow = 0; nRow < nRowCount; ++nRow )
        {
            fprintf( file, "<row this=\"%lx\">\n\r", maRows[nRow].get() );
            for( sal_Int32 nCol = 0; nCol < nColCount; ++nCol )
            {
                CellRef xCell( getCell( nCol, nRow ) );
                fprintf( file, "<cell this=\"%lx\"", xCell.get() );
                
                sal_Int32 nRowSpan = xCell->getRowSpan();
                sal_Int32 nColSpan = xCell->getColumnSpan();
                sal_Bool bMerged = xCell->isMerged();

                if( nColSpan != 1 )
                    fprintf( file, " column-span=\"%ld\"", nColSpan );
                if( nRowSpan != 1 )
                    fprintf( file, " row-span=\"%ld\"", nRowSpan );

                if( bMerged )
                    fprintf( file, " merged=\"true\"" );

                fprintf( file, "/>" );
            }
            fprintf( file, "\n\r</row>\n\r" );
        }

        fprintf( file, "</table>\n\r" );
        fclose( file );
#endif
}

// -----------------------------------------------------------------------------

CellRef TableModel::getCell( sal_Int32 nCol, sal_Int32 nRow ) const
{
    if( ((nRow >= 0) && (nRow < getRowCountImpl())) && (nCol >= 0) && (nCol < getColumnCountImpl()) )
    {
        return maRows[nRow]->maCells[nCol];
    }
    else
    {
        CellRef xRet;
        return xRet;
    }
}

// -----------------------------------------------------------------------------
/*
bool TableModel::getCellPos( const CellRef& xCell, ::sal_Int32& rnCol, ::sal_Int32& rnRow ) const
{
    const sal_Int32 nRowCount = getRowCount();
    const sal_Int32 nColCount = getColumnCount();
    for( rnRow = 0; rnRow < nRowCount; rnRow++ )
    {
        for( rnCol = 0; rnCol < nColCount; rnCol++ )
        {
            if( maRows[rnRow]->maCells[rnCol] == xCell )
            {
                return true;
            }
        }
    }
    return false;
}
*/

// -----------------------------------------------------------------------------

CellRef TableModel::createCell()
{
    CellRef xCell;
    if( mpTableObj )
        mpTableObj->createCell( xCell );
    return xCell;
}

// -----------------------------------------------------------------------------

void TableModel::insertColumns( sal_Int32 nIndex, sal_Int32 nCount )
{
    if( nCount && mpTableObj )
    {
        try
        {
            SdrModel* pModel = mpTableObj->GetModel();

            TableModelNotifyGuard aGuard( this );
            nIndex = insert_range<ColumnVector,ColumnVector::iterator,TableColumnRef>( maColumns, nIndex, nCount );

            sal_Int32 nRows = getRowCountImpl();
            while( nRows-- )
                maRows[nRows]->insertColumns( nIndex, nCount );

            ColumnVector aNewColumns(nCount);
            for( sal_Int32 nOffset = 0; nOffset < nCount; ++nOffset )
            {
                TableColumnRef xNewCol( new TableColumn( this, nIndex+nOffset ) );
                maColumns[nIndex+nOffset] = xNewCol;
                aNewColumns[nOffset] = xNewCol;
            }

            const bool bUndo = pModel && mpTableObj->IsInserted() && pModel->IsUndoEnabled();
            if( bUndo )
            {
                pModel->BegUndo( ImpGetResStr(STR_TABLE_INSCOL) );
                pModel->AddUndo( pModel->GetSdrUndoFactory().CreateUndoGeoObject(*mpTableObj) );

                TableModelRef xThis( this );

                nRows = getRowCountImpl();
                CellVector aNewCells( nCount * nRows );
                CellVector::iterator aCellIter( aNewCells.begin() );

                nRows = getRowCountImpl();
                for( sal_Int32 nRow = 0; nRow < nRows; ++nRow )
                {
                    for( sal_Int32 nOffset = 0; nOffset < nCount; ++nOffset )
                        (*aCellIter++) = getCell( nIndex + nOffset, nRow );
                }

                pModel->AddUndo( new InsertColUndo( xThis, nIndex, aNewColumns, aNewCells ) );
            }

            const sal_Int32 nRowCount = getRowCountImpl();
            // check if cells merge over new columns
            for( sal_Int32 nCol = 0; nCol < nIndex; ++nCol )
            {
                for( sal_Int32 nRow = 0; nRow < nRowCount; ++nRow )
                {
                    CellRef xCell( getCell( nCol, nRow ) );
                    sal_Int32 nColSpan = (xCell.is() && !xCell->isMerged()) ? xCell->getColumnSpan() : 1;
                    if( (nColSpan != 1) && ((nColSpan + nCol ) > nIndex) )
                    {
                        // cell merges over newly created columns, so add the new columns to the merged cell
                        const sal_Int32 nRowSpan = xCell->getRowSpan();
                        nColSpan += nCount;
                        merge( nCol, nRow, nColSpan, nRowSpan );
                    }
                }
            }

            if( bUndo )
                pModel->EndUndo();
                
            if( pModel )
                pModel->SetChanged();

        }
        catch( Exception& )
        {
            DBG_ERROR("sdr::table::TableModel::insertColumns(), exception caught!");
        }
        updateColumns();
        setModified(sal_True);
    }
}

// -----------------------------------------------------------------------------

void TableModel::removeColumns( sal_Int32 nIndex, sal_Int32 nCount )
{
    sal_Int32 nColCount = getColumnCountImpl();

    if( mpTableObj && nCount && (nIndex >= 0) && (nIndex < nColCount) )
    {
        try
        {
            TableModelNotifyGuard aGuard( this );

            // clip removed columns to columns actually avalaible
            if( (nIndex + nCount) > nColCount )
                nCount = nColCount - nIndex;

            sal_Int32 nRows = getRowCountImpl();

            SdrModel* pModel = mpTableObj->GetModel();

            const bool bUndo = pModel && mpTableObj->IsInserted() && pModel->IsUndoEnabled();
            if( bUndo  )
            {
                pModel->BegUndo( ImpGetResStr(STR_UNDO_COL_DELETE) );
                pModel->AddUndo( pModel->GetSdrUndoFactory().CreateUndoGeoObject(*mpTableObj) );

                TableModelRef xThis( this );
                ColumnVector aRemovedCols( nCount );
                sal_Int32 nOffset;
                for( nOffset = 0; nOffset < nCount; ++nOffset )
                {
                    aRemovedCols[nOffset] = maColumns[nIndex+nOffset];
                }

                CellVector aRemovedCells( nCount * nRows );
                CellVector::iterator aCellIter( aRemovedCells.begin() );
                for( sal_Int32 nRow = 0; nRow < nRows; ++nRow )
                {
                    for( nOffset = 0; nOffset < nCount; ++nOffset )
                        (*aCellIter++) = getCell( nIndex + nOffset, nRow );
                }

                pModel->AddUndo( new RemoveColUndo( xThis, nIndex, aRemovedCols, aRemovedCells ) );
            }

            // only rows before and inside the removed rows are considered
            nColCount = nIndex + nCount + 1;

            const sal_Int32 nRowCount = getRowCountImpl();

            // first check merged cells before and inside the removed rows
            for( sal_Int32 nCol = 0; nCol < nColCount; ++nCol )
            {
                for( sal_Int32 nRow = 0; nRow < nRowCount; ++nRow )
                {
                    CellRef xCell( getCell( nCol, nRow ) );
                    sal_Int32 nColSpan = (xCell.is() && !xCell->isMerged()) ? xCell->getColumnSpan() : 1;
                    if( nColSpan <= 1 )
                        continue;

                    if( nCol >= nIndex )
                    {
                        // current cell is inside the removed columns
                        if( (nCol + nColSpan) > ( nIndex + nCount ) )
                        {
                            // current cells merges with columns after the removed columns
                            const sal_Int32 nRemove = nCount - nCol + nIndex;
                            
                            CellRef xTargetCell( getCell( nIndex + nCount, nRow ) );
                            if( xTargetCell.is() )
                            {
                                if( bUndo )
                                    xTargetCell->AddUndo();
                                xTargetCell->merge( nColSpan - nRemove, xCell->getRowSpan() );
                                xTargetCell->replaceContentAndFormating( xCell );
                            }
                        }
                    }
                    else if( nColSpan > (nIndex - nCol) )
                    {
                        // current cells spans inside the removed columns, so adjust					
                        const sal_Int32 nRemove = ::std::min( nCount, nCol + nColSpan - nIndex );
                        if( bUndo )
                            xCell->AddUndo();
                        xCell->merge( nColSpan - nRemove, xCell->getRowSpan() );
                    }
                }
            }

            // now remove the columns
            remove_range<ColumnVector,ColumnVector::iterator>( maColumns, nIndex, nCount );
            while( nRows-- )
                maRows[nRows]->removeColumns( nIndex, nCount );

            if( bUndo )
                pModel->EndUndo();
                
            if( pModel )
                pModel->SetChanged();
        }
        catch( Exception& )
        {
            DBG_ERROR("sdr::table::TableModel::removeColumns(), exception caught!");
        }

        updateColumns();
        setModified(sal_True);
    }
}

// -----------------------------------------------------------------------------

void TableModel::insertRows( sal_Int32 nIndex, sal_Int32 nCount )
{
    if( nCount && mpTableObj )
    {
        SdrModel* pModel = mpTableObj->GetModel();
        const bool bUndo = pModel && mpTableObj->IsInserted() && pModel->IsUndoEnabled();
        try
        {
            TableModelNotifyGuard aGuard( this );

            nIndex = insert_range<RowVector,RowVector::iterator,TableRowRef>( maRows, nIndex, nCount );

            RowVector aNewRows(nCount);
            const sal_Int32 nColCount = getColumnCountImpl();
            for( sal_Int32 nOffset = 0; nOffset < nCount; ++nOffset )
            {
                TableRowRef xNewRow( new TableRow( this, nIndex+nOffset, nColCount ) );
                maRows[nIndex+nOffset] = xNewRow;
                aNewRows[nOffset] = xNewRow;
            }

            if( bUndo )
            {
                pModel->BegUndo( ImpGetResStr(STR_TABLE_INSROW) );
                pModel->AddUndo( pModel->GetSdrUndoFactory().CreateUndoGeoObject(*mpTableObj) );
                TableModelRef xThis( this );
                pModel->AddUndo( new InsertRowUndo( xThis, nIndex, aNewRows ) );
            }

            // check if cells merge over new columns
            for( sal_Int32 nRow = 0; nRow < nIndex; ++nRow )
            {
                for( sal_Int32 nCol = 0; nCol < nColCount; ++nCol )
                {
                    CellRef xCell( getCell( nCol, nRow ) );
                    sal_Int32 nRowSpan = (xCell.is() && !xCell->isMerged()) ? xCell->getRowSpan() : 1;
                    if( (nRowSpan > 1) && ((nRowSpan + nRow) > nIndex) )
                    {
                        // cell merges over newly created columns, so add the new columns to the merged cell
                        const sal_Int32 nColSpan = xCell->getColumnSpan();
                        nRowSpan += nCount;
                        merge( nCol, nRow, nColSpan, nRowSpan );
                    }
                }
            }
        }
        catch( Exception& )
        {
            DBG_ERROR("sdr::table::TableModel::insertRows(), exception caught!");
        }
        if( bUndo )
            pModel->EndUndo();
            
        if( pModel )
            pModel->SetChanged();

        updateRows();
        setModified(sal_True);
    }
}

// -----------------------------------------------------------------------------

void TableModel::removeRows( sal_Int32 nIndex, sal_Int32 nCount )
{
    sal_Int32 nRowCount = getRowCountImpl();

    if( mpTableObj && nCount && (nIndex >= 0) && (nIndex < nRowCount) )
    {
        SdrModel* pModel = mpTableObj->GetModel();
        const bool bUndo = pModel && mpTableObj->IsInserted()&& pModel->IsUndoEnabled();

        try
        {
            TableModelNotifyGuard aGuard( this );

            // clip removed rows to rows actually avalaible
            if( (nIndex + nCount) > nRowCount )
                nCount = nRowCount - nIndex;

            if( bUndo )
            {
                pModel->BegUndo( ImpGetResStr(STR_UNDO_ROW_DELETE) );
                pModel->AddUndo( pModel->GetSdrUndoFactory().CreateUndoGeoObject(*mpTableObj) );

                TableModelRef xThis( this );

                RowVector aRemovedRows( nCount );
                for( sal_Int32 nOffset = 0; nOffset < nCount; ++nOffset )
                    aRemovedRows[nOffset] = maRows[nIndex+nOffset];

                pModel->AddUndo( new RemoveRowUndo( xThis, nIndex, aRemovedRows ) );
            }

            // only rows before and inside the removed rows are considered
            nRowCount = nIndex + nCount + 1;

            const sal_Int32 nColCount = getColumnCountImpl();

            // first check merged cells before and inside the removed rows
            for( sal_Int32 nRow = 0; nRow < nRowCount; ++nRow )
            {
                for( sal_Int32 nCol = 0; nCol < nColCount; ++nCol )
                {
                    CellRef xCell( getCell( nCol, nRow ) );
                    sal_Int32 nRowSpan = (xCell.is() && !xCell->isMerged()) ? xCell->getRowSpan() : 1;
                    if( nRowSpan <= 1 )
                        continue;

                    if( nRow >= nIndex )
                    {
                        // current cell is inside the removed rows
                        if( (nRow + nRowSpan) > (nIndex + nCount) )
                        {
                            // current cells merges with rows after the removed rows
                            const sal_Int32 nRemove = nCount - nRow + nIndex;
                            
                            CellRef xTargetCell( getCell( nCol, nIndex + nCount ) );
                            if( xTargetCell.is() )
                            {
                                if( bUndo )
                                    xTargetCell->AddUndo();
                                xTargetCell->merge( xCell->getColumnSpan(), nRowSpan - nRemove );
                                xTargetCell->replaceContentAndFormating( xCell );
                            }
                        }
                    }
                    else if( nRowSpan > (nIndex - nRow) )
                    {
                        // current cells spans inside the removed rows, so adjust					
                        const sal_Int32 nRemove = ::std::min( nCount, nRow + nRowSpan - nIndex );
                        if( bUndo )
                            xCell->AddUndo();
                        xCell->merge( xCell->getColumnSpan(), nRowSpan - nRemove );
                    }
                }
            }

            // now remove the rows
            remove_range<RowVector,RowVector::iterator>( maRows, nIndex, nCount );

            if( bUndo )
                pModel->EndUndo();
                
            if( pModel )
                pModel->SetChanged();
        }
        catch( Exception& )
        {
            DBG_ERROR("sdr::table::TableModel::removeRows(), exception caught!");
        }

        updateRows();
        setModified(sal_True);
    }
}

// -----------------------------------------------------------------------------

TableRowRef TableModel::getRow( sal_Int32 nRow ) const throw (IndexOutOfBoundsException)
{
    if( (nRow >= 0) && (nRow < getRowCountImpl()) )
        return maRows[nRow];

    throw IndexOutOfBoundsException();
}

// -----------------------------------------------------------------------------

TableColumnRef TableModel::getColumn( sal_Int32 nColumn ) const throw (IndexOutOfBoundsException)
{
    if( (nColumn >= 0) && (nColumn < getColumnCountImpl()) )
        return maColumns[nColumn];

    throw IndexOutOfBoundsException();
}

// -----------------------------------------------------------------------------

/** deletes rows and columns that are completly merged. Must be called between BegUndo/EndUndo! */
void TableModel::optimize()
{
    TableModelNotifyGuard aGuard( this );

    bool bWasModified = false;
    
    if( !maRows.empty() && !maColumns.empty() )
    {
        sal_Int32 nCol = getColumnCountImpl() - 1;
        while( nCol > 0 )
        {
            bool bEmpty = true;
            for( sal_Int32 nRow = 0; (nRow < getRowCountImpl()) && bEmpty; nRow++ )
            {
                Reference< XMergeableCell > xCell( getCellByPosition( nCol, nRow ), UNO_QUERY );
                if( xCell.is() && !xCell->isMerged() )
                    bEmpty = false;
            }
            
            if( bEmpty )
            {
                if( nCol > 0 ) try
                {
                    const OUString sWidth( RTL_CONSTASCII_USTRINGPARAM("Width") );
                    sal_Int32 nWidth1 = 0, nWidth2 = 0;
                    Reference< XPropertySet > xSet1( static_cast< XCellRange* >( maColumns[nCol].get() ), UNO_QUERY_THROW );
                    Reference< XPropertySet > xSet2( static_cast< XCellRange* >( maColumns[nCol-1].get() ), UNO_QUERY_THROW );
                    xSet1->getPropertyValue( sWidth ) >>= nWidth1;
                    xSet2->getPropertyValue( sWidth ) >>= nWidth2;
                    nWidth1 += nWidth2;
                    xSet2->setPropertyValue( sWidth, Any( nWidth1 ) );
                }
                catch( Exception& e )
                {
                    (void)e;
                    DBG_ERROR("svx::TableModel::optimize(), exception caught!");
                }

                removeColumns( nCol, 1 );
                bWasModified = true;
            }

            nCol--;
        }

        sal_Int32 nRow = getRowCountImpl() - 1;
        while( nRow > 0 )
        {
            bool bEmpty = true;
            for( nCol = 0; (nCol < getColumnCountImpl()) && bEmpty; nCol++ )
            {
                Reference< XMergeableCell > xCell( getCellByPosition( nCol, nRow ), UNO_QUERY );
                if( xCell.is() && !xCell->isMerged() )
                    bEmpty = false;
            }
            
            if( bEmpty )
            {
                if( nRow > 0 ) try
                {
                    const OUString sHeight( RTL_CONSTASCII_USTRINGPARAM("Height") );
                    sal_Int32 nHeight1 = 0, nHeight2 = 0;
                    Reference< XPropertySet > xSet1( static_cast< XCellRange* >( maRows[nRow].get() ), UNO_QUERY_THROW );
                    Reference< XPropertySet > xSet2( static_cast< XCellRange* >( maRows[nRow-1].get() ), UNO_QUERY_THROW );
                    xSet1->getPropertyValue( sHeight ) >>= nHeight1;
                    xSet2->getPropertyValue( sHeight ) >>= nHeight2;
                    nHeight1 += nHeight2;
                    xSet2->setPropertyValue( sHeight, Any( nHeight1 ) );
                }
                catch( Exception& e )
                {
                    (void)e;
                    DBG_ERROR("svx::TableModel::optimize(), exception caught!");
                }

                removeRows( nRow, 1 );
                bWasModified = true;
            }

            nRow--;
        }
    }
    if( bWasModified )
        setModified(sal_True);
}

// -----------------------------------------------------------------------------

void TableModel::merge( sal_Int32 nCol, sal_Int32 nRow, sal_Int32 nColSpan, sal_Int32 nRowSpan )
{
    SdrModel* pModel = mpTableObj->GetModel();

    const bool bUndo = pModel && mpTableObj->IsInserted() && pModel->IsUndoEnabled();

    const sal_Int32 nLastRow = nRow + nRowSpan;
    const sal_Int32 nLastCol = nCol + nColSpan;

    if( (nLastRow > getRowCount()) || (nLastCol > getRowCount() ) )
    {
        DBG_ERROR("TableModel::merge(), merge beyound the table!");
    }
        
    // merge first cell
    CellRef xOriginCell( dynamic_cast< Cell* >( getCellByPosition( nCol, nRow ).get() ) );
    if( xOriginCell.is() )
    {
        if( bUndo )
            xOriginCell->AddUndo();
        xOriginCell->merge( nColSpan, nRowSpan );
    }

    sal_Int32 nTempCol = nCol + 1;

    // merge remaining cells
    for( ; nRow < nLastRow; nRow++ )
    {
        for( ; nTempCol < nLastCol; nTempCol++ )
        {
            CellRef xCell( dynamic_cast< Cell* >( getCellByPosition( nTempCol, nRow ).get() ) );
            if( xCell.is() && !xCell->isMerged() )
            {
                if( bUndo )
                    xCell->AddUndo();
                xCell->setMerged();
                xOriginCell->mergeContent( xCell );
            }
        }
        nTempCol = nCol;
    }
}


// -----------------------------------------------------------------------------

void TableModel::updateRows()
{
    sal_Int32 nRow = 0;
    RowVector::iterator iter = maRows.begin();
    while( iter != maRows.end() )
    {
        (*iter++)->mnRow = nRow++;
    }
}

// -----------------------------------------------------------------------------

void TableModel::updateColumns()
{
    sal_Int32 nColumn = 0;
    ColumnVector::iterator iter = maColumns.begin();
    while( iter != maColumns.end() )
    {
        (*iter++)->mnColumn = nColumn++;
    }
}

// -----------------------------------------------------------------------------

} }

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