/* -*- 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::XMo
difyListener >
{
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& 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.25 Sekunden
(vorverarbeitet)
¤
*© Formatika GbR, Deutschland