Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  tablecontroller.cxx   Sprache: C

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

#include <svx/sdr/table/tablecontroller.hxx>
#include <tablemodel.hxx>

#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
#include <com/sun/star/container/XIndexAccess.hpp>

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

#include <sal/config.h>
#include <sal/log.hxx>

#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <vcl/window.hxx>

#include <svl/whiter.hxx>
#include <svl/stritem.hxx>

#include <sfx2/request.hxx>

#include <svx/svdotable.hxx>
#include <sdr/overlay/overlayobjectcell.hxx>
#include <svx/sdr/overlay/overlaymanager.hxx>
#include <svx/svxids.hrc>
#include <svx/svdoutl.hxx>
#include <svx/svdpagv.hxx>
#include <svx/svdetc.hxx>
#include <svx/selectioncontroller.hxx>
#include <svx/svdmodel.hxx>
#include <svx/sdrpaintwindow.hxx>
#include <svx/svxdlg.hxx>
#include <editeng/boxitem.hxx>
#include <cell.hxx>
#include <editeng/borderline.hxx>
#include <editeng/colritem.hxx>
#include <editeng/lineitem.hxx>
#include <svx/strings.hrc>
#include <svx/dialmgr.hxx>
#include <svx/svdpage.hxx>
#include <svx/sdmetitm.hxx>
#include <svx/sdtditm.hxx>
#include "tableundo.hxx"
#include "tablelayouter.hxx"
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <memory>
#include <o3tl/enumarray.hxx>
#include <o3tl/enumrange.hxx>
#include <cppuhelper/implbase.hxx>
#include <comphelper/lok.hxx>
#include <sfx2/viewsh.hxx>
#include <editeng/editview.hxx>
#include <tools/UnitConversion.hxx>
#include <comphelper/diagnose_ex.hxx>

using ::editeng::SvxBorderLine;
using namespace sdr::table;
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::table;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::text;
using namespace ::com::sun::star::style;

namespace {

enum class CellPosFlag  // signals the relative position of a cell to a selection
{
    NONE   = 0x0000, // not set or inside
    // row
    Before = 0x0001,
    Left   = 0x0002,
    Right  = 0x0004,
    After  = 0x0008,
    // column
    Upper  = 0x0010,
    Top    = 0x0020,
    Bottom = 0x0040,
    Lower  = 0x0080
};

}

namespace o3tl
template<> struct typed_flags<CellPosFlag> : is_typed_flags<CellPosFlag, 0xff> {}; }

namespace sdr::table {

class SvxTableControllerModifyListener : public ::cppu::WeakImplHelper< css::util::XModifyListener >
{
public:
    explicit SvxTableControllerModifyListener( SvxTableController* pController )
        : mpController( pController ) {}

    // XModifyListener
    virtual void SAL_CALL modified( const css::lang::EventObject& aEvent ) override;

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

    SvxTableController* mpController;
};

// XModifyListener


void SAL_CALL SvxTableControllerModifyListener::modified( const css::lang::EventObject&  )
{
    if( mpController )
        mpController->onTableModified();
}


// XEventListener


void SAL_CALL SvxTableControllerModifyListener::disposing( const css::lang::EventObject&  )
{
    mpController = nullptr;
}




rtl::Reference< sdr::SelectionController > CreateTableController(
    SdrView& rView,
    const SdrTableObj& rObj,
    const rtl::Reference< sdr::SelectionController >& xRefController )
{
    return SvxTableController::create(rView, rObj, xRefController);
}


rtl::Reference< sdr::SelectionController > SvxTableController::create(
    SdrView& rView,
    const SdrTableObj& rObj,
    const rtl::Reference< sdr::SelectionController >& xRefController )
{
    if( xRefController.is() )
    {
        SvxTableController* pController = dynamic_cast< SvxTableController* >( xRefController.get() );

        if(pController && (pController->mxTableObj.get() == &rObj) && (&pController->mrView == &rView))
        {
            return xRefController;
        }
    }

    return new SvxTableController(rView, rObj);
}


SvxTableController::SvxTableController(
    SdrView& rView,
    const SdrTableObj& rObj)
:   mbCellSelectionMode(false)
    ,mbHasJustMerged(false)
    ,mbLeftButtonDown(false)
    ,mrView(rView)
    ,mxTableObj(const_cast< SdrTableObj* >(&rObj))
    ,mnUpdateEvent( nullptr )
{
    rObj.getActiveCellPos( maCursorFirstPos );
    maCursorLastPos = maCursorFirstPos;

    mxTable = mxTableObj.get()->getUnoTable();
    if( mxTable )
    {
        mxModifyListener = new SvxTableControllerModifyListener( this );
        mxTable->addModifyListener( mxModifyListener );
    }
}

SvxTableController::~SvxTableController()
{
    if( mnUpdateEvent )
    {
        Application::RemoveUserEvent( mnUpdateEvent );
    }

    if( mxModifyListener.is() && mxTableObj.get() )
    {
        rtl::Reference< TableModel > xTable( mxTableObj.get()->getUnoTable() );
        if( xTable.is() )
        {
            xTable->removeModifyListener( mxModifyListener );
            mxModifyListener.clear();
        }
    }
}

bool SvxTableController::onKeyInput(const KeyEvent& rKEvt, vcl::Window* pWindow )
{
    if(!checkTableObject())
        return false;

    SdrTableObj& rTableObj(*mxTableObj.get());
    SdrModel& rModel(rTableObj.getSdrModelFromSdrObject());

    // check if we are read only
    if( rModel.IsReadOnly())
    {
        switch( rKEvt.GetKeyCode().GetCode() )
        {
        case awt::Key::DOWN:
        case awt::Key::UP:
        case awt::Key::LEFT:
        case awt::Key::RIGHT:
        case awt::Key::TAB:
        case awt::Key::HOME:
        case awt::Key::END:
        case awt::Key::NUM2:
        case awt::Key::NUM4:
        case awt::Key::NUM6:
        case awt::Key::NUM8:
        case awt::Key::ESCAPE:
        case awt::Key::F2:
            break;
        default:
            // tell the view we eat the event, no further processing needed
            return true;
        }
    }

    TblAction nAction = getKeyboardAction(rKEvt);

    return executeAction( nAction, rKEvt.GetKeyCode().IsShift(), pWindow );
}

namespace {

Point pixelToLogic(const Point& rPoint, vcl::Window const * pWindow)
{
    if (!pWindow)
        return rPoint;

    return pWindow->PixelToLogic(rPoint);
}

}

bool SvxTableController::onMouseButtonDown(const MouseEvent& rMEvt, vcl::Window* pWindow )
{
    if (comphelper::LibreOfficeKit::isActive() && !pWindow)
    {
        // Tiled rendering: get the window that has the disabled map mode.
        if (OutputDevice* pOutputDevice = mrView.GetFirstOutputDevice())
        {
            if (pOutputDevice->GetOutDevType() == OUTDEV_WINDOW)
                pWindow = pOutputDevice->GetOwnerWindow();
        }
    }

    if( !pWindow || !checkTableObject() )
        return false;

    SdrViewEvent aVEvt;
    if( !rMEvt.IsRight() && mrView.PickAnything(rMEvt,SdrMouseEventKind::BUTTONDOWN, aVEvt) == SdrHitKind::Handle )
        return false;

    TableHitKind eHit = mxTableObj.get()->CheckTableHit(pixelToLogic(rMEvt.GetPosPixel(), pWindow), maMouseDownPos.mnCol, maMouseDownPos.mnRow);

    mbLeftButtonDown = (rMEvt.GetClicks() == 1) && rMEvt.IsLeft();

    if( eHit == TableHitKind::Cell )
    {
        StartSelection( maMouseDownPos );
        return true;
    }

    if( rMEvt.IsRight() && eHit != TableHitKind::NONE )
    {
        OutlinerView* pOLV = mrView.GetTextEditOutlinerView();
        if( pOLV )
            pOLV->MouseButtonDown(rMEvt);
        return true// right click will become context menu
    }

    // for cell selection with the mouse remember our first hit
    if( mbLeftButtonDown )
    {
        RemoveSelection();

        SdrHdl* pHdl = mrView.PickHandle(pixelToLogic(rMEvt.GetPosPixel(), pWindow));

        if( pHdl )
        {
            mbLeftButtonDown = false;
        }
        else
        {
            rtl::Reference<sdr::table::SdrTableObj> pTableObj = mxTableObj.get();

            if (!pTableObj || eHit == TableHitKind::NONE)
            {
                mbLeftButtonDown = false;
            }
        }
    }

    if (comphelper::LibreOfficeKit::isActive() && rMEvt.GetClicks() == 2 && rMEvt.IsLeft() && eHit == TableHitKind::CellTextArea)
    {
        bool bEmptyOutliner = false;
        if (Outliner* pOutliner = mrView.GetTextEditOutliner())
        {
            if (pOutliner->GetParagraphCount() == 1)
            {
                if (Paragraph* pParagraph = pOutliner->GetParagraph(0))
                    bEmptyOutliner = pOutliner->GetText(pParagraph).isEmpty();
            }
        }
        if (bEmptyOutliner)
        {
            // Tiled rendering: a left double-click in an empty cell: select it.
            StartSelection(maMouseDownPos);
            setSelectedCells(maMouseDownPos, maMouseDownPos);
            // Update graphic selection, should be hidden now.
            mrView.AdjustMarkHdl();
            return true;
        }
    }

    return false;
}


bool SvxTableController::onMouseButtonUp(const MouseEvent& rMEvt, vcl::Window* /*pWin*/)
{
    if( !checkTableObject() )
        return false;

    mbLeftButtonDown = false;

    return rMEvt.GetClicks() == 2;
}


bool SvxTableController::onMouseMove(const MouseEvent& rMEvt, vcl::Window* pWindow )
{
    if( !checkTableObject() )
        return false;

    rtl::Reference<SdrTableObj> pTableObj = mxTableObj.get();
    CellPos aPos;
    if (mbLeftButtonDown && pTableObj && pTableObj->CheckTableHit(pixelToLogic(rMEvt.GetPosPixel(), pWindow), aPos.mnCol, aPos.mnRow ) != TableHitKind::NONE)
    {
        if(aPos != maMouseDownPos)
        {
            if( mbCellSelectionMode )
            {
                setSelectedCells( maMouseDownPos, aPos );
                return true;
            }
            else
            {
                StartSelection( maMouseDownPos );
            }
        }
        else if( mbCellSelectionMode )
        {
            UpdateSelection( aPos );
            return true;
        }
    }
    return false;
}


void SvxTableController::onSelectionHasChanged()
{
    bool bSelected = false;

    rtl::Reference<SdrTableObj> pTableObj = mxTableObj.get();
    if( pTableObj && pTableObj->IsTextEditActive() )
    {
        pTableObj->getActiveCellPos( maCursorFirstPos );
        maCursorLastPos = maCursorFirstPos;
        mbCellSelectionMode = false;
    }
    else
    {
        const SdrMarkList& rMarkList= mrView.GetMarkedObjectList();
        if( rMarkList.GetMarkCount() == 1 )
            bSelected = mxTableObj.get().get() == rMarkList.GetMark(0)->GetMarkedSdrObj();
    }

    if( bSelected )
    {
        updateSelectionOverlay();
    }
    else
    {
        destroySelectionOverlay();
    }
}
void SvxTableController::onSelectAll()
{
    rtl::Reference<sdr::table::SdrTableObj> pTableObj = mxTableObj.get();
    if ( pTableObj && !pTableObj->IsTextEditActive())
    {
        selectAll();
    }
}


void SvxTableController::GetState( SfxItemSet& rSet )
{
    if(!mxTable.is() || !mxTableObj.get().is())
        return;

    SdrTableObj& rTableObj(*mxTableObj.get());
    SdrModel& rModel(rTableObj.getSdrModelFromSdrObject());
    std::optional<SfxItemSet> oSet;
    bool bVertDone(false);

    // Iterate over all requested items in the set.
    SfxWhichIter aIter( rSet );
    sal_uInt16 nWhich = aIter.FirstWhich();
    while (nWhich)
    {
        switch (nWhich)
        {
            case SID_TABLE_VERT_BOTTOM:
            case SID_TABLE_VERT_CENTER:
            case SID_TABLE_VERT_NONE:
                {
                    if(!bVertDone)
                    {
                        if (!oSet)
                        {
                            oSet.emplace(rModel.GetItemPool());
                            MergeAttrFromSelectedCells(*oSet, false);
                        }

                        SdrTextVertAdjust eAdj = SDRTEXTVERTADJUST_BLOCK;

                        if (oSet->GetItemState( SDRATTR_TEXT_VERTADJUST ) != SfxItemState::INVALID)
                            eAdj = oSet->Get(SDRATTR_TEXT_VERTADJUST).GetValue();

                        rSet.Put(SfxBoolItem(SID_TABLE_VERT_BOTTOM, eAdj == SDRTEXTVERTADJUST_BOTTOM));
                        rSet.Put(SfxBoolItem(SID_TABLE_VERT_CENTER, eAdj == SDRTEXTVERTADJUST_CENTER));
                        rSet.Put(SfxBoolItem(SID_TABLE_VERT_NONE, eAdj == SDRTEXTVERTADJUST_TOP));
                        bVertDone = true;
                    }
                    break;
                }
            case SID_TABLE_DELETE_ROW:
                if( !mxTable.is() || !hasSelectedCells() || (!comphelper::LibreOfficeKit::isActive() && mxTable->getRowCount() <= 1) )
                    rSet.DisableItem(SID_TABLE_DELETE_ROW);
                break;
            case SID_TABLE_DELETE_COL:
                if( !mxTable.is() || !hasSelectedCells() || (!comphelper::LibreOfficeKit::isActive() && mxTable->getColumnCount() <= 1) )
                    rSet.DisableItem(SID_TABLE_DELETE_COL);
                break;
            case SID_TABLE_DELETE_TABLE:
                if( !mxTable.is() )
                    rSet.DisableItem(SID_TABLE_DELETE_TABLE);
                break;
            case SID_TABLE_MERGE_CELLS:
                if( !mxTable.is() || !hasSelectedCells() )
                    rSet.DisableItem(SID_TABLE_MERGE_CELLS);
                break;
            case SID_TABLE_SPLIT_CELLS:
                if( !hasSelectedCells() || !mxTable.is() )
                    rSet.DisableItem(SID_TABLE_SPLIT_CELLS);
                break;

            case SID_TABLE_OPTIMAL_ROW_HEIGHT:
            case SID_TABLE_DISTRIBUTE_COLUMNS:
            case SID_TABLE_DISTRIBUTE_ROWS:
            {
                bool bDistributeColumns = false;
                bool bDistributeRows = false;
                if( mxTable.is() )
                {
                    CellPos aStart, aEnd;
                    getSelectedCells( aStart, aEnd );

                    bDistributeColumns = aStart.mnCol != aEnd.mnCol;
                    bDistributeRows = aStart.mnRow != aEnd.mnRow;
                }
                if( !bDistributeColumns )
                    rSet.DisableItem(SID_TABLE_DISTRIBUTE_COLUMNS);
                if( !bDistributeRows )
                {
                    rSet.DisableItem(SID_TABLE_OPTIMAL_ROW_HEIGHT);
                    rSet.DisableItem(SID_TABLE_DISTRIBUTE_ROWS);
                }
                break;
            }

            default:
                break;
        }
        nWhich = aIter.NextWhich();
    }
}


void SvxTableController::onInsert( sal_uInt16 nSId, const SfxItemSet* pArgs )
{
    if(!checkTableObject())
        return;

    SdrTableObj& rTableObj(*mxTableObj.get());
    SdrModel& rModel(rTableObj.getSdrModelFromSdrObject());
    bool bInsertAfter = true;
    sal_uInt16 nCount = 0;

    if( pArgs )
    {
        const SfxPoolItem* pItem = nullptr;
        pArgs->GetItemState(nSId, false, &pItem);
        if (pItem)
        {
            nCount = static_cast<const SfxInt16Item*>(pItem)->GetValue();
            if(const SfxBoolItem* pItem2 = pArgs->GetItemIfSet(SID_TABLE_PARAM_INSERT_AFTER))
                bInsertAfter = pItem2->GetValue();
        }
    }

    CellPos aStart, aEnd;
    if( hasSelectedCells() )
    {
        getSelectedCells( aStart, aEnd );
    }
    else
    {
        if( bInsertAfter )
        {
            aStart.mnCol = mxTable->getColumnCount() - 1;
            aStart.mnRow = mxTable->getRowCount() - 1;
            aEnd = aStart;
        }
    }

    if( rTableObj.IsTextEditActive() )
        mrView.SdrEndTextEdit(true);

    RemoveSelection();

    static constexpr OUString sSize( u"Size"_ustr );
    const bool bUndo(rModel.IsUndoEnabled());

    switch( nSId )
    {
    case SID_TABLE_INSERT_COL:
    {
        TableModelNotifyGuard aGuard( mxTable.get() );

        if( bUndo )
        {
            rModel.BegUndo( SvxResId(STR_TABLE_INSCOL) );
            rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoGeoObject(rTableObj));
        }

        Reference< XTableColumns > xCols( mxTable->getColumns() );
        const sal_Int32 nNewColumns = (nCount == 0) ? (aEnd.mnCol - aStart.mnCol + 1) : nCount;
        const sal_Int32 nNewStartColumn = aEnd.mnCol + (bInsertAfter ? 1 : 0);
        xCols->insertByIndex( nNewStartColumn, nNewColumns );

        for( sal_Int32 nOffset = 0; nOffset < nNewColumns; nOffset++ )
        {
            // Resolves fdo#61540
            // On Insert before, the reference column whose size is going to be
            // used for newly created column(s) is wrong. As the new columns are
            // inserted before the reference column, the reference column moved
            // to the new position by no., of new columns i.e (earlier+newcolumns).
            Reference< XPropertySet >(xCols->getByIndex(nNewStartColumn+nOffset), UNO_QUERY_THROW )->
                setPropertyValue( sSize,
                    Reference< XPropertySet >(xCols->getByIndex( bInsertAfter?nNewStartColumn-1:nNewStartColumn+nNewColumns ), UNO_QUERY_THROW )->
                        getPropertyValue( sSize ) );
        }

        // Copy cell properties
        sal_Int32 nPropSrcCol = (bInsertAfter ? aEnd.mnCol : aStart.mnCol + nNewColumns);
        sal_Int32 nRowSpan = 0;
        bool bNewSpan = false;

        for( sal_Int32 nRow = 0; nRow < mxTable->getRowCount(); ++nRow )
        {
            CellRef xSourceCell( mxTable->getCell( nPropSrcCol, nRow ) );

            // When we insert new COLUMNs, we want to copy ROW spans.
            if (xSourceCell.is() && nRowSpan == 0)
            {
                // we are not in a span yet. Let's find out if the current cell is in a span.
                sal_Int32 nColSpan = sal_Int32();
                sal_Int32 nSpanInfoCol = sal_Int32();

                if( xSourceCell->getRowSpan() > 1 )
                {
                    // The current cell is the top-left cell in a span.
                    // Get the span info and propagate it to the target.
                    nRowSpan = xSourceCell->getRowSpan();
                    nColSpan = xSourceCell->getColumnSpan();
                    nSpanInfoCol = nPropSrcCol;
                }
                else if( xSourceCell->isMerged() )
                {
                    // The current cell is a middle cell in a 2D span.
                    // Look for the top-left cell in the span.
                    for( nSpanInfoCol = nPropSrcCol - 1; nSpanInfoCol >= 0; --nSpanInfoCol )
                    {
                        CellRef xMergeInfoCell( mxTable->getCell( nSpanInfoCol, nRow ) );
                        if (xMergeInfoCell.is() && !xMergeInfoCell->isMerged())
                        {
                            nRowSpan = xMergeInfoCell->getRowSpan();
                            nColSpan = xMergeInfoCell->getColumnSpan();
                            break;
                        }
                    }
                    if( nRowSpan == 1 )
                        nRowSpan = 0;
                }

                // The target columns are outside the span; Start a new span.
                if( nRowSpan > 0 && ( nNewStartColumn < nSpanInfoCol || nSpanInfoCol + nColSpan <= nNewStartColumn ) )
                    bNewSpan = true;
            }

            // Now copy the properties from the source to the targets
            for( sal_Int32 nOffset = 0; nOffset < nNewColumns; nOffset++ )
            {
                CellRef xTargetCell( mxTable->getCell( nNewStartColumn + nOffset, nRow ) );
                if( xTargetCell.is() )
                {
                    if( nRowSpan > 0 )
                    {
                        if( bNewSpan )
                            xTargetCell->merge( 1, nRowSpan );
                        else
                            xTargetCell->setMerged();
                    }
                    xTargetCell->copyFormatFrom( xSourceCell );
                }
            }

            if( nRowSpan > 0 )
            {
                --nRowSpan;
                bNewSpan = false;
            }
        }

        if( bUndo )
            rModel.EndUndo();

        aStart.mnCol = nNewStartColumn;
        aStart.mnRow = 0;
        aEnd.mnCol = aStart.mnCol + nNewColumns - 1;
        aEnd.mnRow = mxTable->getRowCount() - 1;
        break;
    }

    case SID_TABLE_INSERT_ROW:
    {
        TableModelNotifyGuard aGuard( mxTable.get() );

        if( bUndo )
        {
            rModel.BegUndo( SvxResId(STR_TABLE_INSROW ) );
            rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoGeoObject(rTableObj));
        }

        Reference< XTableRows > xRows( mxTable->getRows() );
        const sal_Int32 nNewRows = (nCount == 0) ? (aEnd.mnRow - aStart.mnRow + 1) : nCount;
        const sal_Int32 nNewRowStart = aEnd.mnRow + (bInsertAfter ? 1 : 0);
        xRows->insertByIndex( nNewRowStart, nNewRows );

        for( sal_Int32 nOffset = 0; nOffset < nNewRows; nOffset++ )
        {
            Reference< XPropertySet >( xRows->getByIndex( aEnd.mnRow + nOffset + 1 ), UNO_QUERY_THROW )->
                setPropertyValue( sSize,
                    Reference< XPropertySet >( xRows->getByIndex( aStart.mnRow + nOffset ), UNO_QUERY_THROW )->
                        getPropertyValue( sSize ) );
        }

        // Copy the cell properties
        sal_Int32 nPropSrcRow = (bInsertAfter ? aEnd.mnRow : aStart.mnRow + nNewRows);
        sal_Int32 nColSpan = 0;
        bool bNewSpan = false;

        for( sal_Int32 nCol = 0; nCol < mxTable->getColumnCount(); ++nCol )
        {
            CellRef xSourceCell( mxTable->getCell( nCol, nPropSrcRow ) );

            if (!xSourceCell.is())
                continue;

            // When we insert new ROWs, we want to copy COLUMN spans.
            if( nColSpan == 0 )
            {
                // we are not in a span yet. Let's find out if the current cell is in a span.
                sal_Int32 nRowSpan = sal_Int32();
                sal_Int32 nSpanInfoRow = sal_Int32();

                if( xSourceCell->getColumnSpan() > 1 )
                {
                    // The current cell is the top-left cell in a span.
                    // Get the span info and propagate it to the target.
                    nColSpan = xSourceCell->getColumnSpan();
                    nRowSpan = xSourceCell->getRowSpan();
                    nSpanInfoRow = nPropSrcRow;
                }
                else if( xSourceCell->isMerged() )
                {
                    // The current cell is a middle cell in a 2D span.
                    // Look for the top-left cell in the span.
                    for( nSpanInfoRow = nPropSrcRow - 1; nSpanInfoRow >= 0; --nSpanInfoRow )
                    {
                        CellRef xMergeInfoCell( mxTable->getCell( nCol, nSpanInfoRow ) );
                        if (xMergeInfoCell.is() && !xMergeInfoCell->isMerged())
                        {
                            nColSpan = xMergeInfoCell->getColumnSpan();
                            nRowSpan = xMergeInfoCell->getRowSpan();
                            break;
                        }
                    }
                    if( nColSpan == 1 )
                        nColSpan = 0;
                }

                // Inserted rows are outside the span; Start a new span.
                if( nColSpan > 0 && ( nNewRowStart < nSpanInfoRow || nSpanInfoRow + nRowSpan <= nNewRowStart ) )
                    bNewSpan = true;
            }

            // Now copy the properties from the source to the targets
            for( sal_Int32 nOffset = 0; nOffset < nNewRows; ++nOffset )
            {
                CellRef xTargetCell( mxTable->getCell( nCol, nNewRowStart + nOffset ) );
                if( xTargetCell.is() )
                {
                    if( nColSpan > 0 )
                    {
                        if( bNewSpan )
                            xTargetCell->merge( nColSpan, 1 );
                        else
                            xTargetCell->setMerged();
                    }
                    xTargetCell->copyFormatFrom( xSourceCell );
                }
            }

            if( nColSpan > 0 )
            {
                --nColSpan;
                bNewSpan = false;
            }
        }

        if( bUndo )
            rModel.EndUndo();

        aStart.mnCol = 0;
        aStart.mnRow = nNewRowStart;
        aEnd.mnCol = mxTable->getColumnCount() - 1;
        aEnd.mnRow = aStart.mnRow + nNewRows - 1;
        break;
    }
    }

    StartSelection( aStart );
    UpdateSelection( aEnd );
}


void SvxTableController::onDelete( sal_uInt16 nSId )
{
    rtl::Reference<sdr::table::SdrTableObj> pTableObj = mxTableObj.get();
    if( !pTableObj || !mxTable.is() )
        return;

    if( nSId == SID_TABLE_DELETE_TABLE )
    {
        if( pTableObj->IsTextEditActive() )
            mrView.SdrEndTextEdit(true);

        mrView.DeleteMarkedObj();
    }
    else if( hasSelectedCells() )
    {
        CellPos aStart, aEnd;
        getSelectedCells( aStart, aEnd );

        if( pTableObj->IsTextEditActive() )
            mrView.SdrEndTextEdit(true);

        RemoveSelection();

        bool bDeleteTable = false;
        switch( nSId )
        {
        case SID_TABLE_DELETE_COL:
        {
            const sal_Int32 nRemovedColumns = aEnd.mnCol - aStart.mnCol + 1;
            if( nRemovedColumns == mxTable->getColumnCount() )
            {
                bDeleteTable = true;
            }
            else
            {
                Reference< XTableColumns > xCols( mxTable->getColumns() );
                xCols->removeByIndex( aStart.mnCol, nRemovedColumns );
                EditCell(aStart, nullptr, TblAction::NONE);
            }
            break;
        }

        case SID_TABLE_DELETE_ROW:
        {
            const sal_Int32 nRemovedRows = aEnd.mnRow - aStart.mnRow + 1;
            if( nRemovedRows == mxTable->getRowCount() )
            {
                bDeleteTable = true;
            }
            else
            {
                Reference< XTableRows > xRows( mxTable->getRows() );
                xRows->removeByIndex( aStart.mnRow, nRemovedRows );
                EditCell(aStart, nullptr, TblAction::NONE);
            }
            break;
        }
        }

        if( bDeleteTable )
            mrView.DeleteMarkedObj();
        else
            UpdateTableShape();
    }
}


void SvxTableController::onSelect( sal_uInt16 nSId )
{
    if( !mxTable.is() )
        return;

    const sal_Int32 nRowCount = mxTable->getRowCount();
    const sal_Int32 nColCount = mxTable->getColumnCount();
    if( !(nRowCount && nColCount) )
        return;

    CellPos aStart, aEnd;
    getSelectedCells( aStart, aEnd );

    switch( nSId )
    {
    case SID_TABLE_SELECT_ALL:
        aEnd.mnCol = 0; aEnd.mnRow = 0;
        aStart.mnCol = nColCount - 1; aStart.mnRow = nRowCount - 1;
        break;
    case SID_TABLE_SELECT_COL:
        aEnd.mnRow = nRowCount - 1;
        aStart.mnRow = 0;
        break;
    case SID_TABLE_SELECT_ROW:
        aEnd.mnCol = nColCount - 1;
        aStart.mnCol = 0;
        break;
    }

    StartSelection( aEnd );
    gotoCell( aStart, true, nullptr );
}

SvxBoxItem SvxTableController::TextDistancesToSvxBoxItem(const SfxItemSet& rAttrSet)
{
    // merge drawing layer text distance items into SvxBoxItem used by the dialog
    SvxBoxItem aBoxItem( rAttrSet.Get( SDRATTR_TABLE_BORDER ) );
    aBoxItem.SetDistance( sal::static_int_cast< sal_uInt16 >( rAttrSet.Get(SDRATTR_TEXT_LEFTDIST).GetValue()), SvxBoxItemLine::LEFT );
    aBoxItem.SetDistance( sal::static_int_cast< sal_uInt16 >( rAttrSet.Get(SDRATTR_TEXT_RIGHTDIST).GetValue()), SvxBoxItemLine::RIGHT );
    aBoxItem.SetDistance( sal::static_int_cast< sal_uInt16 >( rAttrSet.Get(SDRATTR_TEXT_UPPERDIST).GetValue()), SvxBoxItemLine::TOP );
    aBoxItem.SetDistance( sal::static_int_cast< sal_uInt16 >( rAttrSet.Get(SDRATTR_TEXT_LOWERDIST).GetValue()), SvxBoxItemLine::BOTTOM );
    return aBoxItem;
}

void SvxTableController::SvxBoxItemToTextDistances(const SvxBoxItem& pOriginalItem, SfxItemSet& rAttrSet)
{
    const SvxBoxItem* pNewItem( rAttrSet.GetItemIfSet( SDRATTR_TABLE_BORDER ) );
    if ( !pNewItem )
        return;

    if( pNewItem->GetDistance( SvxBoxItemLine::LEFT ) != pOriginalItem.GetDistance( SvxBoxItemLine::LEFT ) )
        rAttrSet.Put(makeSdrTextLeftDistItem( pNewItem->GetDistance( SvxBoxItemLine::LEFT ) ) );

    if( pNewItem->GetDistance( SvxBoxItemLine::RIGHT ) != pOriginalItem.GetDistance( SvxBoxItemLine::RIGHT ) )
        rAttrSet.Put(makeSdrTextRightDistItem( pNewItem->GetDistance( SvxBoxItemLine::RIGHT ) ) );

    if( pNewItem->GetDistance( SvxBoxItemLine::TOP ) != pOriginalItem.GetDistance( SvxBoxItemLine::TOP ) )
        rAttrSet.Put(makeSdrTextUpperDistItem( pNewItem->GetDistance( SvxBoxItemLine::TOP ) ) );

    if( pNewItem->GetDistance( SvxBoxItemLine::BOTTOM ) != pOriginalItem.GetDistance( SvxBoxItemLine::BOTTOM ) )
        rAttrSet.Put(makeSdrTextLowerDistItem( pNewItem->GetDistance( SvxBoxItemLine::BOTTOM ) ) );
}

void SvxTableController::onFormatTable(const SfxRequest& rReq)
{
    if(!mxTableObj.get().is())
        return;

    SdrTableObj& rTableObj(*mxTableObj.get());
    SdrModel& rModel(rTableObj.getSdrModelFromSdrObject());
    const SfxItemSet* pArgs = rReq.GetArgs();

    if(pArgs)
        return;

    SfxItemSet aNewAttr(rModel.GetItemPool());

    // merge drawing layer text distance items into SvxBoxItem used by the dialog
    auto xBoxItem(std::make_shared<SvxBoxItem>(TextDistancesToSvxBoxItem(aNewAttr)));
    auto xBoxInfoItem(std::make_shared<SvxBoxInfoItem>(aNewAttr.Get(SDRATTR_TABLE_BORDER_INNER)));

    MergeAttrFromSelectedCells(aNewAttr, false);
    FillCommonBorderAttrFromSelectedCells(*xBoxItem, *xBoxInfoItem);
    aNewAttr.Put(*xBoxItem);
    aNewAttr.Put(*xBoxInfoItem);

    // Fill in shadow properties.
    const SfxItemSet& rTableItemSet = rTableObj.GetMergedItemSet();
    for (sal_uInt16 nWhich = SDRATTR_SHADOW_FIRST; nWhich <= SDRATTR_SHADOW_LAST; ++nWhich)
    {
        if (rTableItemSet.GetItemState(nWhich, false) != SfxItemState::SET)
        {
            continue;
        }

        aNewAttr.Put(rTableItemSet.Get(nWhich));
    }

    SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create();
    VclPtr<SfxAbstractTabDialog> xDlg( pFact->CreateSvxFormatCellsDialog(
        rReq.GetFrameWeld(),
        aNewAttr,
        rModel, false) );

    // Even Cancel Button is returning positive(101) value,
    xDlg->StartExecuteAsync([xDlg, this, xBoxItem=std::move(xBoxItem),
                             xBoxInfoItem=std::move(xBoxInfoItem)](int nResult){
        if (nResult == RET_OK)
        {
            SfxItemSet aNewSet(*(xDlg->GetOutputItemSet()));

            //Only properties that were unchanged by the dialog appear in this
            //itemset.  We had constructed these two properties from other
            //ones, so if they were not changed, then forcible set them back to
            //their originals in the new result set so we can decompose that
            //unchanged state back to their input properties
            if (aNewSet.GetItemState(SDRATTR_TABLE_BORDER, false) != SfxItemState::SET)
            {
                aNewSet.Put(*xBoxItem);
            }
            if (aNewSet.GetItemState(SDRATTR_TABLE_BORDER_INNER, false) != SfxItemState::SET)
            {
                aNewSet.Put(*xBoxInfoItem);
            }

            SvxBoxItemToTextDistances(*xBoxItem, aNewSet);

            if (checkTableObject() && mxTable.is())
            {
                // Create a single undo action when applying the result of the dialog.
                SdrTableObj& rTableObject(*mxTableObj.get());
                SdrModel& rSdrModel(rTableObject.getSdrModelFromSdrObject());
                bool bUndo = rSdrModel.IsUndoEnabled() && !mrView.IsTextEdit();
                if (bUndo)
                {
                    rSdrModel.BegUndo(SvxResId(STR_TABLE_NUMFORMAT));
                }

                this->SetAttrToSelectedCells(aNewSet, false);

                this->SetAttrToSelectedShape(aNewSet);

                if (bUndo)
                {
                    rSdrModel.EndUndo();
                }
            }
        }

        xDlg->disposeOnce();
    });
}

void SvxTableController::Execute( SfxRequest& rReq )
{
    const sal_uInt16 nSId = rReq.GetSlot();
    switch( nSId )
    {
    case SID_TABLE_INSERT_ROW:
    case SID_TABLE_INSERT_COL:
        onInsert( nSId, rReq.GetArgs() );
        break;
    case SID_TABLE_DELETE_ROW:
    case SID_TABLE_DELETE_COL:
    case SID_TABLE_DELETE_TABLE:
        onDelete( nSId );
        break;
    case SID_TABLE_SELECT_ALL:
    case SID_TABLE_SELECT_COL:
    case SID_TABLE_SELECT_ROW:
        onSelect( nSId );
        break;
    case SID_FORMAT_TABLE_DLG:
        onFormatTable( rReq );
        break;

    case SID_FRAME_LINESTYLE:
    case SID_FRAME_LINECOLOR:
    case SID_ATTR_BORDER:
        {
            const SfxItemSet* pArgs = rReq.GetArgs();
            if( pArgs )
                ApplyBorderAttr( *pArgs );
        }
        break;

    case SID_ATTR_FILL_STYLE:
        {
            const SfxItemSet* pArgs = rReq.GetArgs();
            if( pArgs )
                SetAttributes( *pArgs, false );
        }
        break;

    case SID_TABLE_MERGE_CELLS:
        MergeMarkedCells();
        break;

    case SID_TABLE_SPLIT_CELLS:
        SplitMarkedCells(rReq);
        break;

    case SID_TABLE_MINIMAL_COLUMN_WIDTH:
        DistributeColumns(/*bOptimize=*/true, /*bMinimize=*/true);
        break;

    case SID_TABLE_OPTIMAL_COLUMN_WIDTH:
        DistributeColumns(/*bOptimize=*/true, /*bMinimize=*/false);
        break;

    case SID_TABLE_DISTRIBUTE_COLUMNS:
        DistributeColumns(/*bOptimize=*/false, /*bMinimize=*/false);
        break;

    case SID_TABLE_MINIMAL_ROW_HEIGHT:
        DistributeRows(/*bOptimize=*/true, /*bMinimize=*/true);
        break;

    case SID_TABLE_OPTIMAL_ROW_HEIGHT:
        DistributeRows(/*bOptimize=*/true, /*bMinimize=*/false);
        break;

    case SID_TABLE_DISTRIBUTE_ROWS:
        DistributeRows(/*bOptimize=*/false, /*bMinimize=*/false);
        break;

    case SID_TABLE_VERT_BOTTOM:
    case SID_TABLE_VERT_CENTER:
    case SID_TABLE_VERT_NONE:
        SetVertical( nSId );
        break;

    case SID_AUTOFORMAT:
    case SID_TABLE_SORT_DIALOG:
    case SID_TABLE_AUTOSUM:
    default:
        break;

    case SID_TABLE_STYLE:
        SetTableStyle( rReq.GetArgs() );
        break;

    case SID_TABLE_STYLE_SETTINGS:
        SetTableStyleSettings( rReq.GetArgs() );
        break;
    case SID_TABLE_CHANGE_CURRENT_BORDER_POSITION:
        changeTableEdge(rReq);
        break;
    }
}

void SvxTableController::SetTableStyle( const SfxItemSet* pArgs )
{
    if(!checkTableObject())
        return;

    SdrTableObj& rTableObj(*mxTableObj.get());
    SdrModel& rModel(rTableObj.getSdrModelFromSdrObject());

    if(!pArgs || (SfxItemState::SET != pArgs->GetItemState(SID_TABLE_STYLE, false)))
        return;

    const SfxStringItem* pArg = &pArgs->Get( SID_TABLE_STYLE );
    if( !(pArg && mxTable.is()) )
        return;

    try
    {
        Reference< XStyleFamiliesSupplier > xSFS( rModel.getUnoModel(), UNO_QUERY_THROW );
        Reference< XNameAccess > xFamilyNameAccess( xSFS->getStyleFamilies(), UNO_SET_THROW );
        Reference< XNameAccess > xTableFamilyAccess( xFamilyNameAccess->getByName( u"table"_ustr ), UNO_QUERY_THROW );

        if( xTableFamilyAccess->hasByName( pArg->GetValue() ) )
        {
            // found table style with the same name
            Reference< XIndexAccess > xNewTableStyle( xTableFamilyAccess->getByName( pArg->GetValue() ), UNO_QUERY_THROW );

            const bool bUndo = rModel.IsUndoEnabled();

            if( bUndo )
            {
                rModel.BegUndo(SvxResId(STR_TABLE_STYLE));
                rModel.AddUndo(std::make_unique<TableStyleUndo>(rTableObj));
            }

            rTableObj.setTableStyle( xNewTableStyle );

            const sal_Int32 nRowCount = mxTable->getRowCount();
            const sal_Int32 nColCount = mxTable->getColumnCount();
            for( sal_Int32 nRow = 0; nRow < nRowCount; nRow++ )
            {
                for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ ) try
                {
                    CellRef xCell( mxTable->getCell( nCol, nRow ) );
                    if( xCell.is() )
                    {
                        SfxItemSet aSet( xCell->GetItemSet() );
                        bool bChanges = false;
                        SfxStyleSheet *pStyleSheet = xCell->GetStyleSheet();
                        SAL_WARN_IF(!pStyleSheet, "svx""no stylesheet for table cell?");
                        if (pStyleSheet)
                        {
                            const SfxItemSet& rStyleAttribs = pStyleSheet->GetItemSet();

                            for ( sal_uInt16 nWhich = SDRATTR_START; nWhich <= SDRATTR_TABLE_LAST; nWhich++ )
                            {
                                if( (rStyleAttribs.GetItemState( nWhich ) == SfxItemState::SET) && (aSet.GetItemState( nWhich ) == SfxItemState::SET) )
                                {
                                    aSet.ClearItem( nWhich );
                                    bChanges = true;
                                }
                            }
                        }

                        if( bChanges )
                        {
                            if( bUndo )
                                xCell->AddUndo();

                            xCell->SetMergedItemSetAndBroadcast( aSet, true );
                        }
                    }
                }
                catch( Exception& )
                {
                    TOOLS_WARN_EXCEPTION("svx.table""");
                }
            }

            if( bUndo )
                rModel.EndUndo();
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION("svx.table""");
    }
}

void SvxTableController::SetTableStyleSettings( const SfxItemSet* pArgs )
{
    if(!checkTableObject())
        return;

    SdrTableObj& rTableObj(*mxTableObj.get());
    SdrModel& rModel(rTableObj.getSdrModelFromSdrObject());

    TableStyleSettings aSettings(rTableObj.getTableStyleSettings() );
    const SfxBoolItem *pPoolItem=nullptr;

    if( (pPoolItem = pArgs->GetItemIfSet(ID_VAL_USEFIRSTROWSTYLE, false)) )
        aSettings.mbUseFirstRow = pPoolItem->GetValue();

    if( (pPoolItem = pArgs->GetItemIfSet(ID_VAL_USELASTROWSTYLE, false)) )
        aSettings.mbUseLastRow = pPoolItem->GetValue();

    if( (pPoolItem = pArgs->GetItemIfSet(ID_VAL_USEBANDINGROWSTYLE, false)) )
        aSettings.mbUseRowBanding = pPoolItem->GetValue();

    if( (pPoolItem = pArgs->GetItemIfSet(ID_VAL_USEFIRSTCOLUMNSTYLE, false)) )
        aSettings.mbUseFirstColumn = pPoolItem->GetValue();

    if( (pPoolItem = pArgs->GetItemIfSet(ID_VAL_USELASTCOLUMNSTYLE, false)) )
        aSettings.mbUseLastColumn = pPoolItem->GetValue();

    if( (pPoolItem = pArgs->GetItemIfSet(ID_VAL_USEBANDINGCOLUMNSTYLE, false)) )
        aSettings.mbUseColumnBanding = pPoolItem->GetValue();

    if( aSettings == rTableObj.getTableStyleSettings() )
        return;

    const bool bUndo(rModel.IsUndoEnabled());

    if( bUndo )
    {
        rModel.BegUndo( SvxResId(STR_TABLE_STYLE_SETTINGS) );
        rModel.AddUndo(std::make_unique<TableStyleUndo>(rTableObj));
    }

    rTableObj.setTableStyleSettings( aSettings );

    if( bUndo )
        rModel.EndUndo();
}

void SvxTableController::SetVertical( sal_uInt16 nSId )
{
    if(!checkTableObject())
        return;

    SdrTableObj& rTableObj(*mxTableObj.get());
    SdrModel& rModel(rTableObj.getSdrModelFromSdrObject());

    TableModelNotifyGuard aGuard( mxTable.get() );
    const bool bUndo(rModel.IsUndoEnabled());

    if (bUndo)
    {
        rModel.BegUndo(SvxResId(STR_TABLE_NUMFORMAT));
        rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoAttrObject(rTableObj));
    }

    CellPos aStart, aEnd;
    getSelectedCells( aStart, aEnd );

    SdrTextVertAdjust eAdj = SDRTEXTVERTADJUST_TOP;

    switch( nSId )
    {
        case SID_TABLE_VERT_BOTTOM:
            eAdj = SDRTEXTVERTADJUST_BOTTOM;
            break;
        case SID_TABLE_VERT_CENTER:
            eAdj = SDRTEXTVERTADJUST_CENTER;
            break;
        //case SID_TABLE_VERT_NONE:
        default:
            break;
    }

    SdrTextVertAdjustItem aItem( eAdj );

    for( sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++ )
    {
        for( sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++ )
        {
            CellRef xCell( mxTable->getCell( nCol, nRow ) );
            if( xCell.is() )
            {
                if (bUndo)
                    xCell->AddUndo();
                SfxItemSet aSet(xCell->GetItemSet());
                aSet.Put(aItem);
                xCell->SetMergedItemSetAndBroadcast(aSet, /*bClearAllItems=*/false);
            }
        }
    }

    UpdateTableShape();

    if (bUndo)
        rModel.EndUndo();
}

void SvxTableController::MergeMarkedCells()
{
    CellPos aStart, aEnd;
    getSelectedCells( aStart, aEnd );
    rtl::Reference<SdrTableObj> pTableObj = mxTableObj.get();
    if( pTableObj )
    {
        if( pTableObj->IsTextEditActive() )
            mrView.SdrEndTextEdit(true);

        TableModelNotifyGuard aGuard( mxTable.get() );
        MergeRange( aStart.mnCol, aStart.mnRow, aEnd.mnCol, aEnd.mnRow );
    }
}

void SvxTableController::SplitMarkedCells(const SfxRequest& rReq)
{
    if(!checkTableObject() || !mxTable.is())
        return;

    SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create();
    VclPtr<SvxAbstractSplitTableDialog> xDlg(pFact->CreateSvxSplitTableDialog(rReq.GetFrameWeld(), false, 99));

    xDlg->StartExecuteAsync([xDlg, this](int) {
        const sal_Int32 nCount = xDlg->GetCount() - 1;

        if( nCount < 1 )
            return;

        CellPos aStart, aEnd;
        getSelectedCells( aStart, aEnd );
        Reference< XMergeableCellRange > xRange( mxTable->createCursorByRange( mxTable->getCellRangeByPosition( aStart.mnCol, aStart.mnRow, aEnd.mnCol, aEnd.mnRow ) ), UNO_QUERY_THROW );
        const sal_Int32 nRowCount = mxTable->getRowCount();
        const sal_Int32 nColCount = mxTable->getColumnCount();
        SdrTableObj& rTableObj(*mxTableObj.get());

        if( rTableObj.IsTextEditActive() )
            mrView.SdrEndTextEdit(true);

        TableModelNotifyGuard aGuard( mxTable.get() );
        SdrModel& rModel(rTableObj.getSdrModelFromSdrObject());
        const bool bUndo(rModel.IsUndoEnabled());

        if( bUndo )
        {
            rModel.BegUndo( SvxResId(STR_TABLE_SPLIT) );
            rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoGeoObject(rTableObj));
        }

        if( xDlg->IsHorizontal() )
        {
            xRange->split( 0, nCount );
        }
        else
        {
            xRange->split( nCount, 0 );
        }

        if( bUndo )
            rModel.EndUndo();

        aEnd.mnRow += mxTable->getRowCount() - nRowCount;
        aEnd.mnCol += mxTable->getColumnCount() - nColCount;

        setSelectedCells( aStart, aEnd );

        xDlg->disposeOnce();
    });
}

void SvxTableController::DistributeColumns(const bool bOptimize, const bool bMinimize)
{
    if(!checkTableObject())
        return;

    SdrTableObj& rTableObj(*mxTableObj.get());
    SdrModel& rModel(rTableObj.getSdrModelFromSdrObject());
    const bool bUndo(rModel.IsUndoEnabled());

    if( bUndo )
    {
        rModel.BegUndo( SvxResId(STR_TABLE_DISTRIBUTE_COLUMNS) );
        rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoGeoObject(rTableObj));
    }

    CellPos aStart, aEnd;
    getSelectedCells( aStart, aEnd );
    rTableObj.DistributeColumns( aStart.mnCol, aEnd.mnCol, bOptimize, bMinimize );

    if( bUndo )
        rModel.EndUndo();
}

void SvxTableController::DistributeRows(const bool bOptimize, const bool bMinimize)
{
    if(!checkTableObject())
        return;

    SdrTableObj& rTableObj(*mxTableObj.get());
    SdrModel& rModel(rTableObj.getSdrModelFromSdrObject());
    const bool bUndo(rModel.IsUndoEnabled());

    if( bUndo )
    {
        rModel.BegUndo( SvxResId(STR_TABLE_DISTRIBUTE_ROWS) );
        rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoGeoObject(rTableObj));
    }

    CellPos aStart, aEnd;
    getSelectedCells( aStart, aEnd );
    rTableObj.DistributeRows( aStart.mnRow, aEnd.mnRow, bOptimize, bMinimize );

    if( bUndo )
        rModel.EndUndo();
}

bool SvxTableController::HasMarked() const
{
    return mbCellSelectionMode && mxTable.is();
}

bool SvxTableController::DeleteMarked()
{
    if(!checkTableObject() || !HasMarked())
        return false;

    SdrTableObj& rTableObj(*mxTableObj.get());
    SdrModel& rModel(rTableObj.getSdrModelFromSdrObject());
    const bool bUndo(rModel.IsUndoEnabled());
    bool bDeleteTable = false;

    if (bUndo)
        rModel.BegUndo(SvxResId(STR_TABLE_DELETE_CELL_CONTENTS));

    CellPos aStart, aEnd;
    getSelectedCells( aStart, aEnd );
    const sal_Int32 nRemovedColumns = aEnd.mnCol - aStart.mnCol + 1;
    const sal_Int32 nRemovedRows = aEnd.mnRow - aStart.mnRow + 1;
    if( nRemovedColumns == mxTable->getColumnCount() && nRemovedRows == mxTable->getRowCount())
    {
        bDeleteTable = true;
    }
    else
    {
        for( sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++ )
        {
            for( sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++ )
            {
                CellRef xCell( mxTable->getCell( nCol, nRow ) );
                if (xCell.is() && xCell->hasText())
                {
                    if (bUndo)
                        xCell->AddUndo();
                    xCell->SetOutlinerParaObject(std::nullopt);
                }
            }
        }
    }

    if (bDeleteTable)
        mrView.DeleteMarkedObj();

    if (bUndo)
        rModel.EndUndo();

    if (!bDeleteTable)
        UpdateTableShape();
    return true;
}

bool SvxTableController::GetStyleSheet( SfxStyleSheet*& rpStyleSheet ) const
{
    if( hasSelectedCells() )
    {
        rpStyleSheet = nullptr;

        if( mxTable.is() )
        {
            SfxStyleSheet* pRet=nullptr;
            bool b1st=true;

            CellPos aStart, aEnd;
            const_cast<SvxTableController&>(*this).getSelectedCells( aStart, aEnd );

            for( sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++ )
            {
                for( sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++ )
                {
                    CellRef xCell( mxTable->getCell( nCol, nRow ) );
                    if( xCell.is() )
                    {
                        SfxStyleSheet* pSS=xCell->GetStyleSheet();
                        if(b1st)
                        {
                            pRet=pSS;
                        }
                        else if(pRet != pSS)
                        {
                            return true;
                        }
                        b1st=false;
                    }
                }
            }
            rpStyleSheet = pRet;
            return true;
        }
    }
    return false;
}

bool SvxTableController::SetStyleSheet( SfxStyleSheet* pStyleSheet, bool bDontRemoveHardAttr )
{
    if( hasSelectedCells() && (!pStyleSheet || pStyleSheet->GetFamily() == SfxStyleFamily::Frame) )
    {
        if( mxTable.is() )
        {
            CellPos aStart, aEnd;
            getSelectedCells( aStart, aEnd );

            for( sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++ )
            {
                for( sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++ )
                {
                    CellRef xCell( mxTable->getCell( nCol, nRow ) );
                    if( xCell.is() )
                        xCell->SetStyleSheet(pStyleSheet,bDontRemoveHardAttr);
                }
            }

            UpdateTableShape();
            return true;
        }
    }
    return false;
}

void SvxTableController::changeTableEdge(const SfxRequest& rReq)
{
    if (!checkTableObject())
        return;

    const auto* pType = rReq.GetArg<SfxStringItem>(SID_TABLE_BORDER_TYPE);
    const auto* pIndex = rReq.GetArg<SfxUInt16Item>(SID_TABLE_BORDER_INDEX);
    const auto* pOffset = rReq.GetArg<SfxInt32Item>(SID_TABLE_BORDER_OFFSET);

    if (!(pType && pIndex && pOffset))
        return;

    const OUString sType = pType->GetValue();
    const sal_uInt16 nIndex = pIndex->GetValue();
    const sal_Int32 nOffset = convertTwipToMm100(pOffset->GetValue());

    SdrTableObj& rTableObj(*mxTableObj.get());

    sal_Int32 nEdgeIndex = -1;
    bool bHorizontal = sType.startsWith("row");

    if (sType == "column-left" || sType == "row-left")
    {
        nEdgeIndex = 0;
    }
    else if (sType == "column-right")
    {
        // Number of edges = number of columns + 1
        nEdgeIndex = rTableObj.getColumnCount();
    }
    else if (sType == "row-right")
    {
        // Number of edges = number of rows + 1
        nEdgeIndex = rTableObj.getRowCount();
    }
    else if (sType == "column-middle" || sType == "row-middle")
    {
        nEdgeIndex = nIndex + 1;
    }

    if (nEdgeIndex < 0)
        return;

    TableModelNotifyGuard aGuard(mxTable.get());
    SdrModel& rModel(rTableObj.getSdrModelFromSdrObject());
    const bool bUndo(rModel.IsUndoEnabled());
    if (bUndo)
    {
        auto pUndoObject = rModel.GetSdrUndoFactory().CreateUndoGeoObject(rTableObj);
        rModel.BegUndo(pUndoObject->GetComment());

        auto* pGeoUndo = static_cast<SdrUndoGeoObj*>(pUndoObject.get());
        if (pGeoUndo)
            pGeoUndo->SetSkipChangeLayout(true);

        rModel.AddUndo(std::move(pUndoObject));
    }
    tools::Rectangle aBoundRect;
    if (rTableObj.GetUserCall())
        aBoundRect = rTableObj.GetLastBoundRect();
    rTableObj.changeEdge(bHorizontal, nEdgeIndex, nOffset);
    rTableObj.SetChanged();
    rTableObj.SendUserCall(SdrUserCallType::Resize, aBoundRect);
    if (bUndo)
        rModel.EndUndo();
}

// internals


bool SvxTableController::checkTableObject()
{
    return mxTableObj.get().is();
}


SvxTableController::TblAction SvxTableController::getKeyboardAction(const KeyEvent&&nbsp;rKEvt)
{
    const bool bMod1 = rKEvt.GetKeyCode().IsMod1(); // ctrl
    const bool bMod2 = rKEvt.GetKeyCode().IsMod2(); // Alt
    const bool bTextEdit = mrView.IsTextEdit();

    TblAction nAction = TblAction::HandledByView;

    rtl::Reference<sdr::table::SdrTableObj> pTableObj = mxTableObj.get();
    if( !pTableObj )
        return nAction;

    // handle special keys
    const sal_Int16 nCode = rKEvt.GetKeyCode().GetCode();
    switch( nCode )
    {
    case awt::Key::ESCAPE:          // handle escape
    {
        if( bTextEdit )
        {
            // escape during text edit ends text edit
            nAction = TblAction::StopTextEdit;
        }
        if( mbCellSelectionMode )
        {
            // escape with selected cells removes selection
            nAction = TblAction::RemoveSelection;
        }
        break;
    }
    case awt::Key::RETURN:      // handle return
    {
        if( !bMod1 && !bMod2 && !bTextEdit )
        {
            // when not already editing, return starts text edit
            setSelectionStart( SdrTableObj::getFirstCell() );
            nAction = TblAction::EditCell;
        }
        break;
    }
    case awt::Key::F2:          // f2 toggles text edit
    {
        if( bMod1 || bMod2 )    // f2 with modifiers is handled by the view
        {
        }
        else if( bTextEdit )
        {
            // f2 during text edit stops text edit
            nAction = TblAction::StopTextEdit;
        }
        else if( mbCellSelectionMode )
        {
            // f2 with selected cells removes selection
            nAction = TblAction::RemoveSelection;
        }
        else
        {
            // f2 with no selection and no text edit starts text edit
            setSelectionStart( SdrTableObj::getFirstCell() );
            nAction = TblAction::EditCell;
        }
        break;
    }
    case awt::Key::HOME:
    case awt::Key::NUM7:
    {
        if( (bMod1 ||  bMod2) && (bTextEdit || mbCellSelectionMode) )
        {
            if( bMod1 && !bMod2 )
            {
                // ctrl + home jumps to first cell
                nAction = TblAction::GotoFirstCell;
            }
            else if( !bMod1 && bMod2 )
            {
                // alt + home jumps to first column
                nAction = TblAction::GotoFirstColumn;
            }
        }
        break;
    }
    case awt::Key::END:
    case awt::Key::NUM1:
    {
        if( (bMod1 ||  bMod2) && (bTextEdit || mbCellSelectionMode) )
        {
            if( bMod1 && !bMod2 )
            {
                // ctrl + end jumps to last cell
                nAction = TblAction::GotoLastCell;
            }
            else if( !bMod1 && bMod2 )
            {
                // alt + home jumps to last column
                nAction = TblAction::GotoLastColumn;
            }
        }
        break;
    }

    case awt::Key::TAB:
    {
        if( bTextEdit || mbCellSelectionMode )
            nAction = TblAction::Tab;
        break;
    }

    case awt::Key::UP:
    case awt::Key::NUM8:
    case awt::Key::DOWN:
    case awt::Key::NUM2:
    case awt::Key::LEFT:
    case awt::Key::NUM4:
    case awt::Key::RIGHT:
    case awt::Key::NUM6:
    {

        if( !bMod1 && bMod2 )
        {
            if(bTextEdit || mbCellSelectionMode)
            {
                if( (nCode == awt::Key::UP) || (nCode == awt::Key::NUM8) )
                {
                    nAction = TblAction::GotoLeftCell;
                    break;
                }
                else if( (nCode == awt::Key::DOWN) || (nCode == awt::Key::NUM2) )
                {
                    nAction = TblAction::GotoRightCell;
                    break;
                }
            }
        }

        bool bTextMove = false;
        OutlinerView* pOLV = mrView.GetTextEditOutlinerView();
        if( pOLV )
        {
            RemoveSelection();
            // during text edit, check if we navigate out of the cell
            ESelection aOldSelection = pOLV->GetSelection();
            pOLV->PostKeyEvent(rKEvt);
            bTextMove = aOldSelection == pOLV->GetSelection();
            if( !bTextMove )
            {
                nAction = TblAction::NONE;
            }
        }

        if( mbCellSelectionMode || bTextMove )
        {
            // no text edit, navigate in cells if selection active
            switch( nCode )
            {
            case awt::Key::LEFT:
            case awt::Key::NUM4:
                nAction = TblAction::GotoLeftCell;
                break;
            case awt::Key::RIGHT:
            case awt::Key::NUM6:
                nAction = TblAction::GotoRightCell;
                break;
            case awt::Key::DOWN:
            case awt::Key::NUM2:
                nAction = TblAction::GotoDownCell;
                break;
            case awt::Key::UP:
            case awt::Key::NUM8:
                nAction = TblAction::GotoUpCell;
                break;
            }
        }
        break;
    }
    case awt::Key::PAGEUP:
        if( bMod2 )
            nAction = TblAction::GotoFirstRow;
        break;

    case awt::Key::PAGEDOWN:
        if( bMod2 )
            nAction = TblAction::GotoLastRow;
        break;
    }
    return nAction;
}

bool SvxTableController::executeAction(TblAction nAction, bool bSelect, vcl::Window* pWindow)
{
    rtl::Reference<sdr::table::SdrTableObj> pTableObj = mxTableObj.get();
    if( !pTableObj )
        return false;

    switch( nAction )
    {
    case TblAction::GotoFirstCell:
    {
        gotoCell( SdrTableObj::getFirstCell(), bSelect, pWindow, nAction );
        break;
    }

    case TblAction::GotoLeftCell:
    {
        gotoCell( pTableObj->getLeftCell( getSelectionEnd(), !bSelect ), bSelect, pWindow, nAction );
        break;
    }

    case TblAction::GotoRightCell:
    {
        gotoCell( pTableObj->getRightCell( getSelectionEnd(), !bSelect ), bSelect, pWindow, nAction);
        break;
    }

    case TblAction::GotoLastCell:
    {
        gotoCell( pTableObj->getLastCell(), bSelect, pWindow, nAction );
        break;
    }

    case TblAction::GotoFirstColumn:
    {
        CellPos aPos( SdrTableObj::getFirstCell().mnCol, getSelectionEnd().mnRow );
        gotoCell( aPos, bSelect, pWindow, nAction );
        break;
    }

    case TblAction::GotoLastColumn:
    {
        CellPos aPos( pTableObj->getLastCell().mnCol, getSelectionEnd().mnRow );
        gotoCell( aPos, bSelect, pWindow, nAction );
        break;
    }

    case TblAction::GotoFirstRow:
    {
        CellPos aPos( getSelectionEnd().mnCol, SdrTableObj::getFirstCell().mnRow );
        gotoCell( aPos, bSelect, pWindow, nAction );
        break;
    }

    case TblAction::GotoUpCell:
    {
        gotoCell( pTableObj->getUpCell(getSelectionEnd(), !bSelect), bSelect, pWindow, nAction );
        break;
    }

    case TblAction::GotoDownCell:
    {
        gotoCell( pTableObj->getDownCell(getSelectionEnd(), !bSelect), bSelect, pWindow, nAction );
        break;
    }

    case TblAction::GotoLastRow:
    {
        CellPos aPos( getSelectionEnd().mnCol, pTableObj->getLastCell().mnRow );
        gotoCell( aPos, bSelect, pWindow, nAction );
        break;
    }

    case TblAction::EditCell:
        EditCell( getSelectionStart(), pWindow, nAction );
        break;

    case TblAction::StopTextEdit:
        StopTextEdit();
        break;

    case TblAction::RemoveSelection:
        RemoveSelection();
        break;

    case TblAction::Tab:
    {
        if( bSelect )
            gotoCell( pTableObj->getPreviousCell( getSelectionEnd(), true ), false, pWindow, nAction );
        else
        {
            CellPos aSelectionEnd( getSelectionEnd() );
            CellPos aNextCell( pTableObj->getNextCell( aSelectionEnd, true ) );
            if( aSelectionEnd == aNextCell )
            {
                onInsert( SID_TABLE_INSERT_ROW );
                aNextCell = pTableObj->getNextCell( aSelectionEnd, true );
            }
            gotoCell( aNextCell, false, pWindow, nAction );
        }
        break;
    }
    default:
        break;
    }

    return nAction != TblAction::HandledByView;
}


void SvxTableController::gotoCell(const CellPos& rPos, bool bSelect, vcl::Window* pWindow, TblAction nAction /*= TblAction::NONE */)
{
    auto pTable = mxTableObj.get();
    if( pTable && pTable->IsTextEditActive() )
        mrView.SdrEndTextEdit(true);

    if( bSelect )
    {
        maCursorLastPos = rPos;
        if( pTable )
            pTable->setActiveCell( rPos );

        if( !mbCellSelectionMode )
        {
            setSelectedCells( maCursorFirstPos, rPos );
        }
        else
        {
            UpdateSelection( rPos );
        }
    }
    else
    {
        RemoveSelection();
        EditCell( rPos, pWindow, nAction );
    }
}


const CellPos& SvxTableController::getSelectionStart()
{
    checkCell( maCursorFirstPos );
    return maCursorFirstPos;
}


void SvxTableController::setSelectionStart( const CellPos& rPos )
{
    maCursorFirstPos = rPos;
}


const CellPos& SvxTableController::getSelectionEnd()
{
    checkCell( maCursorLastPos );
    return maCursorLastPos;
}


void SvxTableController::MergeRange( sal_Int32 nFirstCol, sal_Int32 nFirstRow, sal_Int32 nLastCol, sal_Int32 nLastRow )
{
    if(!checkTableObject() || !mxTable.is())
        return;

    try
    {
        Reference< XMergeableCellRange > xRange( mxTable->createCursorByRange( mxTable->getCellRangeByPosition( nFirstCol, nFirstRow,nLastCol, nLastRow ) ), UNO_QUERY_THROW );

        if( xRange->isMergeable() )
        {
            SdrTableObj& rTableObj(*mxTableObj.get());
            SdrModel& rModel(rTableObj.getSdrModelFromSdrObject());
            const bool bUndo(rModel.IsUndoEnabled());

            if( bUndo )
            {
                rModel.BegUndo( SvxResId(STR_TABLE_MERGE) );
                rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoGeoObject(rTableObj));
            }

            xRange->merge();
            mbHasJustMerged = true;
            setSelectedCells( maCursorFirstPos, maCursorFirstPos );

            if( bUndo )
                rModel.EndUndo();
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "svx.table""" );
    }
}


void SvxTableController::checkCell( CellPos& rPos ) const
{
    if( !mxTable.is() )
        return;

    try
    {
        if( rPos.mnCol >= mxTable->getColumnCount() )
            rPos.mnCol = mxTable->getColumnCount()-1;

        if( rPos.mnRow >= mxTable->getRowCount() )
            rPos.mnRow = mxTable->getRowCount()-1;
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION("svx.table""");
    }
}


void SvxTableController::findMergeOrigin( CellPos& rPos )
{
    if( !mxTable.is() )
        return;

    try
    {
        Reference< XMergeableCell > xCell( mxTable->getCellByPosition( rPos.mnCol, rPos.mnRow ), UNO_QUERY_THROW );
        if( xCell->isMerged() )
        {
            ::findMergeOrigin( mxTable, rPos.mnCol, rPos.mnRow, rPos.mnCol, rPos.mnRow );
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION("svx.table""");
    }
}


void SvxTableController::EditCell(const CellPos& rPos, vcl::Window* pWindow, TblAction nAction /*= TblAction::NONE */)
{
    SdrPageView* pPV(mrView.GetSdrPageView());

    if(nullptr == pPV || !checkTableObject())
        return;

    SdrTableObj& rTableObj(*mxTableObj.get());

    if(rTableObj.getSdrPageFromSdrObject() != pPV->GetPage())
        return;

    bool bEmptyOutliner = false;

    if(!rTableObj.GetOutlinerParaObject() && mrView.GetTextEditOutliner())
    {
        ::Outliner* pOutl = mrView.GetTextEditOutliner();
        sal_Int32 nParaCnt = pOutl->GetParagraphCount();
        Paragraph* p1stPara = pOutl->GetParagraph( 0 );

        if(nParaCnt==1 && p1stPara)
        {
            // with only one paragraph
            if (pOutl->GetText(p1stPara).isEmpty())
            {
                bEmptyOutliner = true;
            }
        }
    }

    CellPos aPos( rPos );
    findMergeOrigin( aPos );

    if( &rTableObj == mrView.GetTextEditObject() && !bEmptyOutliner && rTableObj.IsTextEditActive( aPos ) )
        return;

    if( rTableObj.IsTextEditActive() )
        mrView.SdrEndTextEdit(true);

    rTableObj.setActiveCell( aPos );

    // create new outliner, owner will be the SdrObjEditView
    SdrModel& rModel(rTableObj.getSdrModelFromSdrObject());
    std::unique_ptr<SdrOutliner> pOutl(SdrMakeOutliner(OutlinerMode::OutlineObject, rModel));

    if (pOutl && rTableObj.IsVerticalWriting())
        pOutl->SetVertical( true );

    if (!mrView.SdrBeginTextEdit(&rTableObj, pPV, pWindow, true, pOutl.release()))
        return;

    maCursorLastPos = maCursorFirstPos = rPos;

    OutlinerView* pOLV = mrView.GetTextEditOutlinerView();

    // Move cursor to end of text
    ESelection aNewSelection;

    const WritingMode eMode = rTableObj.GetWritingMode();
    if (((nAction == TblAction::GotoLeftCell) || (nAction == TblAction::GotoRightCell)) && (eMode != WritingMode_TB_RL))
    {
        const bool bLast = ((nAction == TblAction::GotoLeftCell) && (eMode == WritingMode_LR_TB)) ||
                             ((nAction == TblAction::GotoRightCell) && (eMode == WritingMode_RL_TB));

        if( bLast )
            aNewSelection = ESelection::AtEnd();
    }
    pOLV->SetSelection(aNewSelection);
}


void SvxTableController::StopTextEdit()
{
    if(mrView.IsTextEdit())
    {
        mrView.SdrEndTextEdit();
        mrView.SetCurrentObj(SdrObjKind::Table);
        mrView.SetEditMode(SdrViewEditMode::Edit);
    }
}


void SvxTableController::getSelectedCells( CellPos& rFirst, CellPos& rLast )
{
    if( mbCellSelectionMode )
    {
        checkCell( maCursorFirstPos );
        checkCell( maCursorLastPos );

        rFirst.mnCol = std::min( maCursorFirstPos.mnCol, maCursorLastPos.mnCol );
        rFirst.mnRow = std::min( maCursorFirstPos.mnRow, maCursorLastPos.mnRow );
        rLast.mnCol = std::max( maCursorFirstPos.mnCol, maCursorLastPos.mnCol );
        rLast.mnRow = std::max( maCursorFirstPos.mnRow, maCursorLastPos.mnRow );

        if( !mxTable.is() )
            return;

        bool bExt = false;
        do
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=94 H=96 G=94

¤ Dauer der Verarbeitung: 0.24 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge