From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- svx/source/table/tablecontroller.cxx | 3363 ++++++++++++++++++++++++++++++++++ 1 file changed, 3363 insertions(+) create mode 100644 svx/source/table/tablecontroller.cxx (limited to 'svx/source/table/tablecontroller.cxx') diff --git a/svx/source/table/tablecontroller.cxx b/svx/source/table/tablecontroller.cxx new file mode 100644 index 000000000..1ceaa5141 --- /dev/null +++ b/svx/source/table/tablecontroller.cxx @@ -0,0 +1,3363 @@ +/* -*- 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 + +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tableundo.hxx" +#include "tablelayouter.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 : is_typed_flags {}; } + +namespace sdr::table { + +namespace { + +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 ) +{ + mxTableObj->getActiveCellPos( maCursorFirstPos ); + maCursorLastPos = maCursorFirstPos; + + Reference< XTable > xTable( mxTableObj->getTable() ); + if( xTable.is() ) + { + mxModifyListener = new SvxTableControllerModifyListener( this ); + xTable->addModifyListener( mxModifyListener ); + + mxTable.set( dynamic_cast< TableModel* >( xTable.get() ) ); + } +} + +SvxTableController::~SvxTableController() +{ + if( mnUpdateEvent ) + { + Application::RemoveUserEvent( mnUpdateEvent ); + } + + if( mxModifyListener.is() && mxTableObj ) + { + Reference< XTable > xTable( mxTableObj->getTable() ); + if( xTable.is() ) + { + xTable->removeModifyListener( mxModifyListener ); + mxModifyListener.clear(); + } + } +} + +bool SvxTableController::onKeyInput(const KeyEvent& rKEvt, vcl::Window* pWindow ) +{ + if(!checkTableObject()) + return false; + + SdrTableObj& rTableObj(*mxTableObj); + 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->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 ) + 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 + { + 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; + + 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; + + 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() == rMarkList.GetMark(0)->GetMarkedSdrObj(); + } + + if( bSelected ) + { + updateSelectionOverlay(); + } + else + { + destroySelectionOverlay(); + } +} +void SvxTableController::onSelectAll() +{ + sdr::table::SdrTableObj* pTableObj = mxTableObj.get(); + if ( pTableObj && !pTableObj->IsTextEditActive()) + { + selectAll(); + } +} + + +void SvxTableController::GetState( SfxItemSet& rSet ) +{ + if(!mxTable.is() || !mxTableObj.is()) + return; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + std::optional 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::DONTCARE) + 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); + 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(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 const OUStringLiteral sSize( u"Size" ); + 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( dynamic_cast< Cell* >( mxTable->getCellByPosition( nPropSrcCol, nRow ).get() ) ); + + // 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( dynamic_cast< Cell* >( mxTable->getCellByPosition( nSpanInfoCol, nRow ).get() ) ); + 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( dynamic_cast< Cell* >( mxTable->getCellByPosition( nNewStartColumn + nOffset, nRow ).get() ) ); + 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( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nPropSrcRow ).get() ) ); + + 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( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nSpanInfoRow ).get() ) ); + 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( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nNewRowStart + nOffset ).get() ) ); + 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 ) +{ + 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 ); +} + +namespace +{ + SvxBoxItem mergeDrawinglayerTextDistancesAndSvxBoxItem(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::onFormatTable(const SfxRequest& rReq) +{ + if(!mxTableObj.is()) + return; + + SdrTableObj& rTableObj(*mxTableObj); + 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 + SvxBoxItem aBoxItem(mergeDrawinglayerTextDistancesAndSvxBoxItem(aNewAttr)); + + SvxBoxInfoItem aBoxInfoItem( aNewAttr.Get( SDRATTR_TABLE_BORDER_INNER ) ); + + MergeAttrFromSelectedCells(aNewAttr, false); + FillCommonBorderAttrFromSelectedCells( aBoxItem, aBoxInfoItem ); + aNewAttr.Put( aBoxItem ); + aNewAttr.Put( aBoxInfoItem ); + + // 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 xDlg( pFact->CreateSvxFormatCellsDialog( + rReq.GetFrameWeld(), + &aNewAttr, + rModel) ); + + // Even Cancel Button is returning positive(101) value, + xDlg->StartExecuteAsync([xDlg, this, aBoxItem, aBoxInfoItem](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(aBoxItem); + } + if (aNewSet.GetItemState(SDRATTR_TABLE_BORDER_INNER, false) != SfxItemState::SET) + { + aNewSet.Put(aBoxInfoItem); + } + + SvxBoxItem aNewBoxItem( aNewSet.Get( SDRATTR_TABLE_BORDER ) ); + + if( aNewBoxItem.GetDistance( SvxBoxItemLine::LEFT ) != aBoxItem.GetDistance( SvxBoxItemLine::LEFT ) ) + aNewSet.Put(makeSdrTextLeftDistItem( aNewBoxItem.GetDistance( SvxBoxItemLine::LEFT ) ) ); + + if( aNewBoxItem.GetDistance( SvxBoxItemLine::RIGHT ) != aBoxItem.GetDistance( SvxBoxItemLine::RIGHT ) ) + aNewSet.Put(makeSdrTextRightDistItem( aNewBoxItem.GetDistance( SvxBoxItemLine::RIGHT ) ) ); + + if( aNewBoxItem.GetDistance( SvxBoxItemLine::TOP ) != aBoxItem.GetDistance( SvxBoxItemLine::TOP ) ) + aNewSet.Put(makeSdrTextUpperDistItem( aNewBoxItem.GetDistance( SvxBoxItemLine::TOP ) ) ); + + if( aNewBoxItem.GetDistance( SvxBoxItemLine::BOTTOM ) != aBoxItem.GetDistance( SvxBoxItemLine::BOTTOM ) ) + aNewSet.Put(makeSdrTextLowerDistItem( aNewBoxItem.GetDistance( SvxBoxItemLine::BOTTOM ) ) ); + + if (checkTableObject() && mxTable.is()) + { + // Create a single undo action when applying the result of the dialog. + SdrTableObj& rTableObject(*mxTableObj); + 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); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + + if(!pArgs || (SfxItemState::SET != pArgs->GetItemState(SID_TABLE_STYLE, false))) + return; + + const SfxStringItem* pArg = dynamic_cast< const SfxStringItem* >( &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( "table" ), 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(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( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + 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); + 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(rTableObj)); + } + + rTableObj.setTableStyleSettings( aSettings ); + + if( bUndo ) + rModel.EndUndo(); +} + +void SvxTableController::SetVertical( sal_uInt16 nSId ) +{ + if(!checkTableObject()) + return; + + SdrTableObj& rTableObj(*mxTableObj); + 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( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + 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 ); + 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 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); + + 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); + 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); + 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); + 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( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + 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(*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( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + 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( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + 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(SID_TABLE_BORDER_TYPE); + const auto* pIndex = rReq.GetArg(SID_TABLE_BORDER_INDEX); + const auto* pOffset = rReq.GetArg(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); + + 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(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.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; + + 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) +{ + 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 */) +{ + if( mxTableObj.is() && mxTableObj->IsTextEditActive() ) + mrView.SdrEndTextEdit(true); + + if( bSelect ) + { + maCursorLastPos = rPos; + if( mxTableObj.is() ) + mxTableObj->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); + 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); + + 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 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(EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND, EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND); + } + 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 + { + bExt = false; + for( sal_Int32 nRow = rFirst.mnRow; nRow <= rLast.mnRow && !bExt; nRow++ ) + { + for( sal_Int32 nCol = rFirst.mnCol; nCol <= rLast.mnCol && !bExt; nCol++ ) + { + Reference< XMergeableCell > xCell( mxTable->getCellByPosition( nCol, nRow ), UNO_QUERY ); + if( !xCell.is() ) + continue; + + if( xCell->isMerged() ) + { + CellPos aPos( nCol, nRow ); + findMergeOrigin( aPos ); + if( (aPos.mnCol < rFirst.mnCol) || (aPos.mnRow < rFirst.mnRow) ) + { + rFirst.mnCol = std::min( rFirst.mnCol, aPos.mnCol ); + rFirst.mnRow = std::min( rFirst.mnRow, aPos.mnRow ); + bExt = true; + } + } + else + { + if( ((nCol + xCell->getColumnSpan() - 1) > rLast.mnCol) || (nRow + xCell->getRowSpan() - 1 ) > rLast.mnRow ) + { + rLast.mnCol = std::max( rLast.mnCol, nCol + xCell->getColumnSpan() - 1 ); + rLast.mnRow = std::max( rLast.mnRow, nRow + xCell->getRowSpan() - 1 ); + bExt = true; + } + } + } + } + } + while(bExt); + } + else if(mrView.IsTextEdit()) + { + rFirst = getSelectionStart(); + findMergeOrigin( rFirst ); + rLast = rFirst; + + if( mxTable.is() ) + { + Reference< XMergeableCell > xCell( mxTable->getCellByPosition( rLast.mnCol, rLast.mnRow ), UNO_QUERY ); + if( xCell.is() ) + { + rLast.mnCol += xCell->getColumnSpan() - 1; + rLast.mnRow += xCell->getRowSpan() - 1; + } + } + } + else + { + rFirst.mnCol = 0; + rFirst.mnRow = 0; + if( mxTable.is() ) + { + rLast.mnRow = mxTable->getRowCount()-1; + rLast.mnCol = mxTable->getColumnCount()-1; + } + else + { + rLast.mnRow = 0; + rLast.mnCol = 0; + } + } +} + + +void SvxTableController::StartSelection( const CellPos& rPos ) +{ + StopTextEdit(); + mbCellSelectionMode = true; + maCursorLastPos = maCursorFirstPos = rPos; + mrView.MarkListHasChanged(); +} + + +void SvxTableController::setSelectedCells( const CellPos& rStart, const CellPos& rEnd ) +{ + StopTextEdit(); + mbCellSelectionMode = true; + maCursorFirstPos = rStart; + UpdateSelection( rEnd ); +} + + +bool SvxTableController::ChangeFontSize(bool bGrow, const FontList* pFontList) +{ + if(!checkTableObject() || !mxTable.is()) + return false; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + + if (mrView.IsTextEdit()) + return true; + + CellPos aStart, aEnd; + + if(hasSelectedCells()) + { + getSelectedCells(aStart, aEnd); + } + else + { + aStart.mnRow = 0; + aStart.mnCol = 0; + aEnd.mnRow = mxTable->getRowCount() - 1; + aEnd.mnCol = mxTable->getColumnCount() - 1; + } + + for (sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++) + { + for (sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++) + { + CellRef xCell(dynamic_cast< Cell* >(mxTable->getCellByPosition(nCol, nRow).get())); + if (xCell.is()) + { + if (rModel.IsUndoEnabled()) + xCell->AddUndo(); + + SfxItemSet aCellSet(xCell->GetItemSet()); + if (EditView::ChangeFontSize(bGrow, aCellSet, pFontList)) + { + xCell->SetMergedItemSetAndBroadcast(aCellSet, false); + } + } + } + } + + UpdateTableShape(); + + return true; +} + + +void SvxTableController::UpdateSelection( const CellPos& rPos ) +{ + maCursorLastPos = rPos; + mrView.MarkListHasChanged(); +} + + +void SvxTableController::clearSelection() +{ + RemoveSelection(); +} + + +void SvxTableController::selectAll() +{ + if( mxTable.is() ) + { + CellPos aPos2( mxTable->getColumnCount()-1, mxTable->getRowCount()-1 ); + if( (aPos2.mnCol >= 0) && (aPos2.mnRow >= 0) ) + { + CellPos aPos1; + setSelectedCells( aPos1, aPos2 ); + } + } +} + + +void SvxTableController::RemoveSelection() +{ + if( mbCellSelectionMode ) + { + mbCellSelectionMode = false; + mrView.MarkListHasChanged(); + } +} + + +void SvxTableController::onTableModified() +{ + if( mnUpdateEvent == nullptr ) + mnUpdateEvent = Application::PostUserEvent( LINK( this, SvxTableController, UpdateHdl ) ); +} + + +void SvxTableController::updateSelectionOverlay() +{ + // There is no need to update selection overlay after merging cells + // since the selection overlay should remain the same + if ( mbHasJustMerged ) + return; + + destroySelectionOverlay(); + if( !mbCellSelectionMode ) + return; + + sdr::table::SdrTableObj* pTableObj = mxTableObj.get(); + if( !pTableObj ) + return; + + sdr::overlay::OverlayObjectCell::RangeVector aRanges; + + tools::Rectangle aStartRect, aEndRect; + CellPos aStart,aEnd; + getSelectedCells( aStart, aEnd ); + pTableObj->getCellBounds( aStart, aStartRect ); + + basegfx::B2DRange a2DRange( basegfx::B2DPoint(aStartRect.Left(), aStartRect.Top()) ); + a2DRange.expand( basegfx::B2DPoint(aStartRect.Right(), aStartRect.Bottom()) ); + + findMergeOrigin( aEnd ); + pTableObj->getCellBounds( aEnd, aEndRect ); + a2DRange.expand( basegfx::B2DPoint(aEndRect.Left(), aEndRect.Top()) ); + a2DRange.expand( basegfx::B2DPoint(aEndRect.Right(), aEndRect.Bottom()) ); + aRanges.push_back( a2DRange ); + + ::Color aHighlight( COL_BLUE ); + OutputDevice* pOutDev = mrView.GetFirstOutputDevice(); + if( pOutDev ) + aHighlight = pOutDev->GetSettings().GetStyleSettings().GetHighlightColor(); + + const sal_uInt32 nCount = mrView.PaintWindowCount(); + for( sal_uInt32 nIndex = 0; nIndex < nCount; nIndex++ ) + { + SdrPaintWindow* pPaintWindow = mrView.GetPaintWindow(nIndex); + if( pPaintWindow ) + { + const rtl::Reference < sdr::overlay::OverlayManager >& xOverlayManager = pPaintWindow->GetOverlayManager(); + if( xOverlayManager.is() ) + { + std::unique_ptr pOverlay(new sdr::overlay::OverlayObjectCell( aHighlight, std::vector(aRanges) )); + + xOverlayManager->add(*pOverlay); + mpSelectionOverlay.emplace(); + mpSelectionOverlay->append(std::move(pOverlay)); + } + } + } + + // If tiled rendering, emit callbacks for sdr table selection. + if (!(pOutDev && comphelper::LibreOfficeKit::isActive())) + return; + + tools::Rectangle aSelection(a2DRange.getMinX(), a2DRange.getMinY(), a2DRange.getMaxX(), a2DRange.getMaxY()); + + if (pOutDev->GetMapMode().GetMapUnit() == MapUnit::Map100thMM) + aSelection = o3tl::toTwips(aSelection, o3tl::Length::mm100); + + if(SfxViewShell* pViewShell = SfxViewShell::Current()) + { + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_SELECTION_AREA, aSelection.toString().getStr()); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, aSelection.toString().getStr()); + } +} + + +void SvxTableController::destroySelectionOverlay() +{ + if( !mpSelectionOverlay ) + return; + + mpSelectionOverlay.reset(); + + if (comphelper::LibreOfficeKit::isActive()) + { + // Clear the LOK text selection so far provided by this table. + if(SfxViewShell* pViewShell = SfxViewShell::Current()) + { + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_SELECTION_AREA, "EMPTY"); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_START, "EMPTY"); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_END, "EMPTY"); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, "EMPTY"); + } + } +} + + +void SvxTableController::MergeAttrFromSelectedCells(SfxItemSet& rAttr, bool bOnlyHardAttr) const +{ + if( !mxTable.is() ) + return; + + CellPos aStart, aEnd; + const_cast(*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( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( xCell.is() && !xCell->isMerged() ) + { + const SfxItemSet& rSet = xCell->GetItemSet(); + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich(aIter.FirstWhich()); + while(nWhich) + { + SfxItemState nState = aIter.GetItemState(false); + if(!bOnlyHardAttr) + { + if(SfxItemState::DONTCARE == nState) + rAttr.InvalidateItem(nWhich); + else + rAttr.MergeValue(rSet.Get(nWhich), true); + } + else if(SfxItemState::SET == nState) + { + const SfxPoolItem& rItem = rSet.Get(nWhich); + rAttr.MergeValue(rItem, true); + } + + nWhich = aIter.NextWhich(); + } + } + } + } +} + + +static void ImplSetLinePreserveColor( SvxBoxItem& rNewFrame, const SvxBorderLine* pNew, SvxBoxItemLine nLine ) +{ + if( pNew ) + { + const SvxBorderLine* pOld = rNewFrame.GetLine(nLine); + if( pOld ) + { + SvxBorderLine aNewLine( *pNew ); + aNewLine.SetColor( pOld->GetColor() ); + rNewFrame.SetLine( &aNewLine, nLine ); + return; + } + } + rNewFrame.SetLine( pNew, nLine ); +} + + +static void ImplApplyBoxItem( CellPosFlag nCellPosFlags, const SvxBoxItem* pBoxItem, const SvxBoxInfoItem* pBoxInfoItem, SvxBoxItem& rNewFrame ) +{ + if (nCellPosFlags & (CellPosFlag::Before|CellPosFlag::After|CellPosFlag::Upper|CellPosFlag::Lower)) + { + // current cell is outside the selection + + if (!(nCellPosFlags & (CellPosFlag::Before|CellPosFlag::After))) // check if it's not any corner + { + if (nCellPosFlags & CellPosFlag::Upper) + { + if( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::TOP) ) + rNewFrame.SetLine(nullptr, SvxBoxItemLine::BOTTOM ); + } + else if (nCellPosFlags & CellPosFlag::Lower) + { + if( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::BOTTOM) ) + rNewFrame.SetLine( nullptr, SvxBoxItemLine::TOP ); + } + } + else if (!(nCellPosFlags & (CellPosFlag::Upper|CellPosFlag::Lower))) // check if it's not any corner + { + if (nCellPosFlags & CellPosFlag::Before) + { + if( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) ) + rNewFrame.SetLine( nullptr, SvxBoxItemLine::RIGHT ); + } + else if (nCellPosFlags & CellPosFlag::After) + { + if( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) ) + rNewFrame.SetLine( nullptr, SvxBoxItemLine::LEFT ); + } + } + } + else + { + // current cell is inside the selection + + if ((nCellPosFlags & CellPosFlag::Left) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) + : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT)) + rNewFrame.SetLine( (nCellPosFlags & CellPosFlag::Left) ? pBoxItem->GetLeft() : pBoxInfoItem->GetVert(), SvxBoxItemLine::LEFT ); + + if( (nCellPosFlags & CellPosFlag::Right) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) ) + rNewFrame.SetLine( (nCellPosFlags & CellPosFlag::Right) ? pBoxItem->GetRight() : pBoxInfoItem->GetVert(), SvxBoxItemLine::RIGHT ); + + if( (nCellPosFlags & CellPosFlag::Top) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::TOP) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::HORI) ) + rNewFrame.SetLine( (nCellPosFlags & CellPosFlag::Top) ? pBoxItem->GetTop() : pBoxInfoItem->GetHori(), SvxBoxItemLine::TOP ); + + if( (nCellPosFlags & CellPosFlag::Bottom) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::BOTTOM) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::HORI) ) + rNewFrame.SetLine( (nCellPosFlags & CellPosFlag::Bottom) ? pBoxItem->GetBottom() : pBoxInfoItem->GetHori(), SvxBoxItemLine::BOTTOM ); + + // apply distance to borders + if( pBoxInfoItem->IsValid( SvxBoxInfoItemValidFlags::DISTANCE ) ) + for( SvxBoxItemLine nLine : o3tl::enumrange() ) + rNewFrame.SetDistance( pBoxItem->GetDistance( nLine ), nLine ); + } +} + + +static void ImplSetLineColor( SvxBoxItem& rNewFrame, SvxBoxItemLine nLine, const Color& rColor ) +{ + const SvxBorderLine* pSourceLine = rNewFrame.GetLine( nLine ); + if( pSourceLine ) + { + SvxBorderLine aLine( *pSourceLine ); + aLine.SetColor( rColor ); + rNewFrame.SetLine( &aLine, nLine ); + } +} + + +static void ImplApplyLineColorItem( CellPosFlag nCellPosFlags, const SvxColorItem* pLineColorItem, SvxBoxItem& rNewFrame ) +{ + const Color aColor( pLineColorItem->GetValue() ); + + if (!(nCellPosFlags & (CellPosFlag::Lower|CellPosFlag::Before|CellPosFlag::After))) + ImplSetLineColor( rNewFrame, SvxBoxItemLine::BOTTOM, aColor ); + + if (!(nCellPosFlags & (CellPosFlag::Upper|CellPosFlag::Before|CellPosFlag::After))) + ImplSetLineColor( rNewFrame, SvxBoxItemLine::TOP, aColor ); + + if (!(nCellPosFlags & (CellPosFlag::Upper|CellPosFlag::Lower|CellPosFlag::After))) + ImplSetLineColor( rNewFrame, SvxBoxItemLine::RIGHT, aColor ); + + if (!(nCellPosFlags & (CellPosFlag::Upper|CellPosFlag::Lower|CellPosFlag::Before))) + ImplSetLineColor( rNewFrame, SvxBoxItemLine::LEFT, aColor ); +} + + +static void ImplApplyBorderLineItem( CellPosFlag nCellPosFlags, const SvxBorderLine* pBorderLineItem, SvxBoxItem& rNewFrame ) +{ + if (nCellPosFlags & (CellPosFlag::Before|CellPosFlag::After|CellPosFlag::Upper|CellPosFlag::Lower)) + { + if (!(nCellPosFlags & (CellPosFlag::Before|CellPosFlag::After))) // check if it's not any corner + { + if (nCellPosFlags & CellPosFlag::Upper) + { + if( rNewFrame.GetBottom() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::BOTTOM ); + } + else if (nCellPosFlags & CellPosFlag::Lower) + { + if( rNewFrame.GetTop() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::TOP ); + } + } + else if (!(nCellPosFlags & (CellPosFlag::Upper|CellPosFlag::Lower))) // check if it's not any corner + { + if (nCellPosFlags & CellPosFlag::Before) + { + if( rNewFrame.GetRight() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::RIGHT ); + } + else if (nCellPosFlags & CellPosFlag::After) + { + if( rNewFrame.GetLeft() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::LEFT ); + } + } + } + else + { + if( rNewFrame.GetBottom() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::BOTTOM ); + if( rNewFrame.GetTop() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::TOP ); + if( rNewFrame.GetRight() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::RIGHT ); + if( rNewFrame.GetLeft() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::LEFT ); + } +} + + +void SvxTableController::ApplyBorderAttr( const SfxItemSet& rAttr ) +{ + if( !mxTable.is() ) + return; + + const sal_Int32 nRowCount = mxTable->getRowCount(); + const sal_Int32 nColCount = mxTable->getColumnCount(); + if( !(nRowCount && nColCount) ) + return; + + const SvxBoxItem* pBoxItem = nullptr; + if(SfxItemState::SET == rAttr.GetItemState(SDRATTR_TABLE_BORDER, false) ) + pBoxItem = &rAttr.Get( SDRATTR_TABLE_BORDER ); + + const SvxBoxInfoItem* pBoxInfoItem = nullptr; + if(SfxItemState::SET == rAttr.GetItemState(SDRATTR_TABLE_BORDER_INNER, false) ) + pBoxInfoItem = &rAttr.Get( SDRATTR_TABLE_BORDER_INNER ); + + const SvxColorItem* pLineColorItem = nullptr; + if(SfxItemState::SET == rAttr.GetItemState(SID_FRAME_LINECOLOR, false) ) + pLineColorItem = &rAttr.Get( SID_FRAME_LINECOLOR ); + + const SvxBorderLine* pBorderLineItem = nullptr; + if(SfxItemState::SET == rAttr.GetItemState(SID_FRAME_LINESTYLE, false) ) + pBorderLineItem = rAttr.Get( SID_FRAME_LINESTYLE ).GetLine(); + + if( pBoxInfoItem && !pBoxItem ) + { + const static SvxBoxItem gaEmptyBoxItem( SDRATTR_TABLE_BORDER ); + pBoxItem = &gaEmptyBoxItem; + } + else if( pBoxItem && !pBoxInfoItem ) + { + const static SvxBoxInfoItem gaEmptyBoxInfoItem( SDRATTR_TABLE_BORDER_INNER ); + pBoxInfoItem = &gaEmptyBoxInfoItem; + } + + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + + const sal_Int32 nLastRow = std::min( aEnd.mnRow + 2, nRowCount ); + const sal_Int32 nLastCol = std::min( aEnd.mnCol + 2, nColCount ); + + for( sal_Int32 nRow = std::max( aStart.mnRow - 1, sal_Int32(0) ); nRow < nLastRow; nRow++ ) + { + CellPosFlag nRowFlags = CellPosFlag::NONE; + nRowFlags |= (nRow == aStart.mnRow) ? CellPosFlag::Top : CellPosFlag::NONE; + nRowFlags |= (nRow == aEnd.mnRow) ? CellPosFlag::Bottom : CellPosFlag::NONE; + nRowFlags |= (nRow < aStart.mnRow) ? CellPosFlag::Upper : CellPosFlag::NONE; + nRowFlags |= (nRow > aEnd.mnRow) ? CellPosFlag::Lower : CellPosFlag::NONE; + + for( sal_Int32 nCol = std::max( aStart.mnCol - 1, sal_Int32(0) ); nCol < nLastCol; nCol++ ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( !xCell.is() ) + continue; + + const SfxItemSet& rSet = xCell->GetItemSet(); + const SvxBoxItem* pOldOuter = &rSet.Get( SDRATTR_TABLE_BORDER ); + + SvxBoxItem aNewFrame( *pOldOuter ); + + CellPosFlag nCellPosFlags = nRowFlags; + nCellPosFlags |= (nCol == aStart.mnCol) ? CellPosFlag::Left : CellPosFlag::NONE; + nCellPosFlags |= (nCol == aEnd.mnCol) ? CellPosFlag::Right : CellPosFlag::NONE; + nCellPosFlags |= (nCol < aStart.mnCol) ? CellPosFlag::Before : CellPosFlag::NONE; + nCellPosFlags |= (nCol > aEnd.mnCol) ? CellPosFlag::After : CellPosFlag::NONE; + + if( pBoxItem && pBoxInfoItem ) + ImplApplyBoxItem( nCellPosFlags, pBoxItem, pBoxInfoItem, aNewFrame ); + + if( pLineColorItem ) + ImplApplyLineColorItem( nCellPosFlags, pLineColorItem, aNewFrame ); + + if( pBorderLineItem ) + ImplApplyBorderLineItem( nCellPosFlags, pBorderLineItem, aNewFrame ); + + if (aNewFrame != *pOldOuter) + { + SfxItemSet aAttr(*rSet.GetPool(), rSet.GetRanges()); + aAttr.Put(aNewFrame); + xCell->SetMergedItemSetAndBroadcast( aAttr, false ); + } + } + } +} + + +void SvxTableController::UpdateTableShape() +{ + SdrObject* pTableObj = mxTableObj.get(); + if( pTableObj ) + { + pTableObj->ActionChanged(); + pTableObj->BroadcastObjectChange(); + } + updateSelectionOverlay(); +} + + +void SvxTableController::SetAttrToSelectedCells(const SfxItemSet& rAttr, bool bReplaceAll) +{ + if(!checkTableObject() || !mxTable.is()) + return; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + const bool bUndo(rModel.IsUndoEnabled()); + + if( bUndo ) + rModel.BegUndo( SvxResId(STR_TABLE_NUMFORMAT) ); + + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + + SfxItemSet aAttr(*rAttr.GetPool(), rAttr.GetRanges()); + aAttr.Put(rAttr); + + const bool bFrame = (rAttr.GetItemState( SDRATTR_TABLE_BORDER ) == SfxItemState::SET) || (rAttr.GetItemState( SDRATTR_TABLE_BORDER_INNER ) == SfxItemState::SET); + + if( bFrame ) + { + aAttr.ClearItem( SDRATTR_TABLE_BORDER ); + aAttr.ClearItem( SDRATTR_TABLE_BORDER_INNER ); + } + + for( sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++ ) + { + for( sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++ ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( xCell.is() ) + { + if( bUndo ) + xCell->AddUndo(); + xCell->SetMergedItemSetAndBroadcast(aAttr, bReplaceAll); + } + } + } + + if( bFrame ) + { + ApplyBorderAttr( rAttr ); + } + + UpdateTableShape(); + + if( bUndo ) + rModel.EndUndo(); +} + +void SvxTableController::SetAttrToSelectedShape(const SfxItemSet& rAttr) +{ + if (!checkTableObject() || !mxTable.is()) + return; + + // Filter out non-shadow items from rAttr. + SfxItemSetFixed aSet(*rAttr.GetPool()); + aSet.Put(rAttr); + + if (!aSet.Count()) + { + // If there are no items to be applied on the shape, then don't set anything, it would + // terminate text edit without a good reason, which affects undo/redo. + return; + } + + // Set shadow items on the marked shape. + mrView.SetAttrToMarked(aSet, /*bReplaceAll=*/false); +} + +bool SvxTableController::GetAttributes(SfxItemSet& rTargetSet, bool bOnlyHardAttr) const +{ + if( mxTableObj.is() && hasSelectedCells() ) + { + MergeAttrFromSelectedCells( rTargetSet, bOnlyHardAttr ); + + if( mrView.IsTextEdit() ) + { + OutlinerView* pTextEditOutlinerView = mrView.GetTextEditOutlinerView(); + if(pTextEditOutlinerView) + { + // FALSE= consider InvalidItems not as the default, but as "holes" + rTargetSet.Put(pTextEditOutlinerView->GetAttribs(), false); + } + } + + return true; + } + else + { + return false; + } +} + + +bool SvxTableController::SetAttributes(const SfxItemSet& rSet, bool bReplaceAll) +{ + if( mbCellSelectionMode || mrView.IsTextEdit() ) + { + SetAttrToSelectedCells( rSet, bReplaceAll ); + return true; + } + return false; +} + +SdrObject* SvxTableController::GetMarkedSdrObjClone(SdrModel& rTargetModel) +{ + SdrTableObj* pRetval(nullptr); + sdr::table::SdrTableObj* pCurrentSdrTableObj(GetTableObj()); + + if(nullptr == pCurrentSdrTableObj) + { + return pRetval; + } + + if(!mxTableObj.is()) + { + return pRetval; + } + + // get selection and create full selection + CellPos aStart, aEnd; + const CellPos aFullStart, aFullEnd(mxTable->getColumnCount()-1, mxTable->getRowCount()-1); + + getSelectedCells(aStart, aEnd); + + // compare to see if we have a partial selection + if(aStart != aFullStart || aEnd != aFullEnd) + { + // create full clone + pRetval = pCurrentSdrTableObj->CloneSdrObject(rTargetModel); + + // limit SdrObject's TableModel to partial selection + pRetval->CropTableModelToSelection(aStart, aEnd); + } + + return pRetval; +} + +bool SvxTableController::PasteObjModel( const SdrModel& rModel ) +{ + if( mxTableObj.is() && (rModel.GetPageCount() >= 1) ) + { + const SdrPage* pPastePage = rModel.GetPage(0); + if( pPastePage && pPastePage->GetObjCount() == 1 ) + { + SdrTableObj* pPasteTableObj = dynamic_cast< SdrTableObj* >( pPastePage->GetObj(0) ); + if( pPasteTableObj ) + { + return PasteObject( pPasteTableObj ); + } + } + } + + return false; +} + + +bool SvxTableController::PasteObject( SdrTableObj const * pPasteTableObj ) +{ + if( !pPasteTableObj ) + return false; + + Reference< XTable > xPasteTable( pPasteTableObj->getTable() ); + if( !xPasteTable.is() ) + return false; + + if( !mxTable.is() ) + return false; + + sal_Int32 nPasteColumns = xPasteTable->getColumnCount(); + sal_Int32 nPasteRows = xPasteTable->getRowCount(); + + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + + if( mrView.IsTextEdit() ) + mrView.SdrEndTextEdit(true); + + sal_Int32 nColumns = mxTable->getColumnCount(); + sal_Int32 nRows = mxTable->getRowCount(); + + const sal_Int32 nMissing = nPasteRows - ( nRows - aStart.mnRow ); + if( nMissing > 0 ) + { + Reference< XTableRows > xRows( mxTable->getRows() ); + xRows->insertByIndex( nRows, nMissing ); + nRows = mxTable->getRowCount(); + } + + nPasteRows = std::min( nPasteRows, nRows - aStart.mnRow ); + nPasteColumns = std::min( nPasteColumns, nColumns - aStart.mnCol ); + + // copy cell contents + for( sal_Int32 nRow = 0; nRow < nPasteRows; ++nRow ) + { + for( sal_Int32 nCol = 0, nTargetCol = aStart.mnCol; nCol < nPasteColumns; ++nCol ) + { + CellRef xTargetCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nTargetCol, aStart.mnRow + nRow ).get() ) ); + if( xTargetCell.is() && !xTargetCell->isMerged() ) + { + CellRef xSourceCell(dynamic_cast(xPasteTable->getCellByPosition(nCol, nRow).get())); + if (xSourceCell.is()) + { + xTargetCell->AddUndo(); + // Prevent cell span exceeding the pasting range. + if (nColumns < nTargetCol + xSourceCell->getColumnSpan()) + xTargetCell->replaceContentAndFormatting(xSourceCell); + else + xTargetCell->cloneFrom(xSourceCell); + + nCol += xSourceCell->getColumnSpan() - 1; + nTargetCol += xTargetCell->getColumnSpan(); + } + } + } + } + + UpdateTableShape(); + + return true; +} + +bool SvxTableController::ApplyFormatPaintBrush( SfxItemSet& rFormatSet, bool bNoCharacterFormats, bool bNoParagraphFormats ) +{ + if(!mbCellSelectionMode) + { + return false; + } + + if(!checkTableObject()) + return false; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + const bool bUndo(rModel.IsUndoEnabled()); + + if( bUndo ) + rModel.BegUndo(SvxResId(STR_TABLE_NUMFORMAT)); + + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + const bool bFrame = (rFormatSet.GetItemState( SDRATTR_TABLE_BORDER ) == SfxItemState::SET) || (rFormatSet.GetItemState( SDRATTR_TABLE_BORDER_INNER ) == SfxItemState::SET); + + for( sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++ ) + { + for( sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++ ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( xCell.is() ) + { + if (bUndo) + xCell->AddUndo(); + SdrText* pText = xCell.get(); + SdrObjEditView::ApplyFormatPaintBrushToText( rFormatSet, rTableObj, pText, bNoCharacterFormats, bNoParagraphFormats ); + } + } + } + + if( bFrame ) + { + ApplyBorderAttr( rFormatSet ); + } + + UpdateTableShape(); + + if( bUndo ) + rModel.EndUndo(); + + return true; +} + + +IMPL_LINK_NOARG(SvxTableController, UpdateHdl, void*, void) +{ + mnUpdateEvent = nullptr; + + if( mbCellSelectionMode ) + { + CellPos aStart( maCursorFirstPos ); + CellPos aEnd( maCursorLastPos ); + checkCell(aStart); + checkCell(aEnd); + if( aStart != maCursorFirstPos || aEnd != maCursorLastPos ) + { + setSelectedCells( aStart, aEnd ); + } + } + + updateSelectionOverlay(); + mbHasJustMerged = false; +} + +namespace +{ + +struct LinesState +{ + LinesState(SvxBoxItem& rBoxItem_, SvxBoxInfoItem& rBoxInfoItem_) + : rBoxItem(rBoxItem_) + , rBoxInfoItem(rBoxInfoItem_) + , bDistanceIndeterminate(false) + { + aBorderSet.fill(false); + aInnerLineSet.fill(false); + aBorderIndeterminate.fill(false); + aInnerLineIndeterminate.fill(false); + aDistanceSet.fill(false); + aDistance.fill(0); + } + + SvxBoxItem& rBoxItem; + SvxBoxInfoItem& rBoxInfoItem; + o3tl::enumarray aBorderSet; + o3tl::enumarray aInnerLineSet; + o3tl::enumarray aBorderIndeterminate; + o3tl::enumarray aInnerLineIndeterminate; + o3tl::enumarray aDistanceSet; + o3tl::enumarray aDistance; + bool bDistanceIndeterminate; +}; + +class BoxItemWrapper +{ +public: + BoxItemWrapper(SvxBoxItem& rBoxItem, SvxBoxInfoItem& rBoxInfoItem, SvxBoxItemLine nBorderLine, SvxBoxInfoItemLine nInnerLine, bool bBorder); + + const SvxBorderLine* getLine() const; + void setLine(const SvxBorderLine* pLine); + +private: + SvxBoxItem& m_rBoxItem; + SvxBoxInfoItem& m_rBoxInfoItem; + const SvxBoxItemLine m_nBorderLine; + const SvxBoxInfoItemLine m_nInnerLine; + const bool m_bBorder; +}; + +BoxItemWrapper::BoxItemWrapper( + SvxBoxItem& rBoxItem, SvxBoxInfoItem& rBoxInfoItem, + const SvxBoxItemLine nBorderLine, const SvxBoxInfoItemLine nInnerLine, const bool bBorder) + : m_rBoxItem(rBoxItem) + , m_rBoxInfoItem(rBoxInfoItem) + , m_nBorderLine(nBorderLine) + , m_nInnerLine(nInnerLine) + , m_bBorder(bBorder) +{ +} + +const SvxBorderLine* BoxItemWrapper::getLine() const +{ + if (m_bBorder) + return m_rBoxItem.GetLine(m_nBorderLine); + else + return (m_nInnerLine == SvxBoxInfoItemLine::HORI) ? m_rBoxInfoItem.GetHori() : m_rBoxInfoItem.GetVert(); +} + +void BoxItemWrapper::setLine(const SvxBorderLine* pLine) +{ + if (m_bBorder) + m_rBoxItem.SetLine(pLine, m_nBorderLine); + else + m_rBoxInfoItem.SetLine(pLine, m_nInnerLine); +} + +void lcl_MergeBorderLine( + LinesState& rLinesState, const SvxBorderLine* const pLine, const SvxBoxItemLine nLine, + SvxBoxInfoItemValidFlags nValidFlag, const bool bBorder = true) +{ + const SvxBoxInfoItemLine nInnerLine(bBorder ? SvxBoxInfoItemLine::HORI : ((nValidFlag & SvxBoxInfoItemValidFlags::HORI) ? SvxBoxInfoItemLine::HORI : SvxBoxInfoItemLine::VERT)); + BoxItemWrapper aBoxItem(rLinesState.rBoxItem, rLinesState.rBoxInfoItem, nLine, nInnerLine, bBorder); + bool& rbSet(bBorder ? rLinesState.aBorderSet[nLine] : rLinesState.aInnerLineSet[nInnerLine]); + + if (rbSet) + { + bool& rbIndeterminate(bBorder ? rLinesState.aBorderIndeterminate[nLine] : rLinesState.aInnerLineIndeterminate[nInnerLine]); + if (!rbIndeterminate) + { + const SvxBorderLine* const pMergedLine(aBoxItem.getLine()); + if ((pLine && !pMergedLine) || (!pLine && pMergedLine) || (pLine && (*pLine != *pMergedLine))) + { + aBoxItem.setLine(nullptr); + rbIndeterminate = true; + } + } + } + else + { + aBoxItem.setLine(pLine); + rbSet = true; + } +} + +void lcl_MergeBorderOrInnerLine( + LinesState& rLinesState, const SvxBorderLine* const pLine, const SvxBoxItemLine nLine, + SvxBoxInfoItemValidFlags nValidFlag, const bool bBorder) +{ + if (bBorder) + lcl_MergeBorderLine(rLinesState, pLine, nLine, nValidFlag); + else + { + const bool bVertical = (nLine == SvxBoxItemLine::LEFT) || (nLine == SvxBoxItemLine::RIGHT); + lcl_MergeBorderLine(rLinesState, pLine, nLine, bVertical ? SvxBoxInfoItemValidFlags::VERT : SvxBoxInfoItemValidFlags::HORI, false); + } +} + +void lcl_MergeDistance( + LinesState& rLinesState, const SvxBoxItemLine nIndex, const sal_uInt16 nDistance) +{ + if (rLinesState.aDistanceSet[nIndex]) + { + if (!rLinesState.bDistanceIndeterminate) + rLinesState.bDistanceIndeterminate = nDistance != rLinesState.aDistance[nIndex]; + } + else + { + rLinesState.aDistance[nIndex] = nDistance; + rLinesState.aDistanceSet[nIndex] = true; + } +} + +void lcl_MergeCommonBorderAttr(LinesState& rLinesState, const SvxBoxItem& rCellBoxItem, const CellPosFlag nCellPosFlags) +{ + if (nCellPosFlags & (CellPosFlag::Before|CellPosFlag::After|CellPosFlag::Upper|CellPosFlag::Lower)) + { + // current cell is outside the selection + + if (!(nCellPosFlags & (CellPosFlag::Before|CellPosFlag::After))) // check if it's not any corner + { + if (nCellPosFlags & CellPosFlag::Upper) + lcl_MergeBorderLine(rLinesState, rCellBoxItem.GetBottom(), SvxBoxItemLine::TOP, SvxBoxInfoItemValidFlags::TOP); + else if (nCellPosFlags & CellPosFlag::Lower) + lcl_MergeBorderLine(rLinesState, rCellBoxItem.GetTop(), SvxBoxItemLine::BOTTOM, SvxBoxInfoItemValidFlags::BOTTOM); + } + else if (!(nCellPosFlags & (CellPosFlag::Upper|CellPosFlag::Lower))) // check if it's not any corner + { + if (nCellPosFlags & CellPosFlag::Before) + lcl_MergeBorderLine(rLinesState, rCellBoxItem.GetRight(), SvxBoxItemLine::LEFT, SvxBoxInfoItemValidFlags::LEFT); + else if (nCellPosFlags & CellPosFlag::After) + lcl_MergeBorderLine(rLinesState, rCellBoxItem.GetLeft(), SvxBoxItemLine::RIGHT, SvxBoxInfoItemValidFlags::RIGHT); + } + + // NOTE: inner distances for cells outside the selected range + // are not relevant -> we ignore them. + } + else + { + // current cell is inside the selection + + lcl_MergeBorderOrInnerLine(rLinesState, rCellBoxItem.GetTop(), SvxBoxItemLine::TOP, SvxBoxInfoItemValidFlags::TOP, static_cast(nCellPosFlags & CellPosFlag::Top)); + lcl_MergeBorderOrInnerLine(rLinesState, rCellBoxItem.GetBottom(), SvxBoxItemLine::BOTTOM, SvxBoxInfoItemValidFlags::BOTTOM, static_cast(nCellPosFlags & CellPosFlag::Bottom)); + lcl_MergeBorderOrInnerLine(rLinesState, rCellBoxItem.GetLeft(), SvxBoxItemLine::LEFT, SvxBoxInfoItemValidFlags::LEFT, static_cast(nCellPosFlags & CellPosFlag::Left)); + lcl_MergeBorderOrInnerLine(rLinesState, rCellBoxItem.GetRight(), SvxBoxItemLine::RIGHT, SvxBoxInfoItemValidFlags::RIGHT, static_cast(nCellPosFlags & CellPosFlag::Right)); + + lcl_MergeDistance(rLinesState, SvxBoxItemLine::TOP, rCellBoxItem.GetDistance(SvxBoxItemLine::TOP)); + lcl_MergeDistance(rLinesState, SvxBoxItemLine::BOTTOM, rCellBoxItem.GetDistance(SvxBoxItemLine::BOTTOM)); + lcl_MergeDistance(rLinesState, SvxBoxItemLine::LEFT, rCellBoxItem.GetDistance(SvxBoxItemLine::LEFT)); + lcl_MergeDistance(rLinesState, SvxBoxItemLine::RIGHT, rCellBoxItem.GetDistance(SvxBoxItemLine::RIGHT)); + } +} + +} + +void SvxTableController::FillCommonBorderAttrFromSelectedCells( SvxBoxItem& rBoxItem, SvxBoxInfoItem& rBoxInfoItem ) const +{ + if( !mxTable.is() ) + return; + + const sal_Int32 nRowCount = mxTable->getRowCount(); + const sal_Int32 nColCount = mxTable->getColumnCount(); + if( !(nRowCount && nColCount) ) + return; + + CellPos aStart, aEnd; + const_cast< SvxTableController* >( this )->getSelectedCells( aStart, aEnd ); + + // We are adding one more row/column around the block of selected cells. + // We will be checking the adjoining border of these too. + const sal_Int32 nLastRow = std::min( aEnd.mnRow + 2, nRowCount ); + const sal_Int32 nLastCol = std::min( aEnd.mnCol + 2, nColCount ); + + rBoxInfoItem.SetValid( SvxBoxInfoItemValidFlags::ALL, false ); + LinesState aLinesState( rBoxItem, rBoxInfoItem ); + + /* Here we go through all the selected cells (enhanced by + * the adjoining row/column on each side) and determine the + * lines for presentation. The algorithm is simple: + * 1. if a border or inner line is set (or unset) in all + * cells to the same value, it will be used. + * 2. if a border or inner line is set only in some cells, + * it will be set to indeterminate state (SetValid() on + * rBoxInfoItem). + */ + for( sal_Int32 nRow = std::max( aStart.mnRow - 1, sal_Int32(0) ); nRow < nLastRow; nRow++ ) + { + CellPosFlag nRowFlags = CellPosFlag::NONE; + nRowFlags |= (nRow == aStart.mnRow) ? CellPosFlag::Top : CellPosFlag::NONE; + nRowFlags |= (nRow == aEnd.mnRow) ? CellPosFlag::Bottom : CellPosFlag::NONE; + nRowFlags |= (nRow < aStart.mnRow) ? CellPosFlag::Upper : CellPosFlag::NONE; + nRowFlags |= (nRow > aEnd.mnRow) ? CellPosFlag::Lower : CellPosFlag::NONE; + + for( sal_Int32 nCol = std::max( aStart.mnCol - 1, sal_Int32(0) ); nCol < nLastCol; nCol++ ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( !xCell.is() ) + continue; + + CellPosFlag nCellPosFlags = nRowFlags; + nCellPosFlags |= (nCol == aStart.mnCol) ? CellPosFlag::Left : CellPosFlag::NONE; + nCellPosFlags |= (nCol == aEnd.mnCol) ? CellPosFlag::Right : CellPosFlag::NONE; + nCellPosFlags |= (nCol < aStart.mnCol) ? CellPosFlag::Before : CellPosFlag::NONE; + nCellPosFlags |= (nCol > aEnd.mnCol) ? CellPosFlag::After : CellPosFlag::NONE; + + const SfxItemSet& rSet = xCell->GetItemSet(); + SvxBoxItem aCellBoxItem(mergeDrawinglayerTextDistancesAndSvxBoxItem(rSet)); + lcl_MergeCommonBorderAttr( aLinesState, aCellBoxItem, nCellPosFlags ); + } + } + + if (!aLinesState.aBorderIndeterminate[SvxBoxItemLine::TOP]) + aLinesState.rBoxInfoItem.SetValid(SvxBoxInfoItemValidFlags::TOP); + if (!aLinesState.aBorderIndeterminate[SvxBoxItemLine::BOTTOM]) + aLinesState.rBoxInfoItem.SetValid(SvxBoxInfoItemValidFlags::BOTTOM); + if (!aLinesState.aBorderIndeterminate[SvxBoxItemLine::LEFT]) + aLinesState.rBoxInfoItem.SetValid(SvxBoxInfoItemValidFlags::LEFT); + if (!aLinesState.aBorderIndeterminate[SvxBoxItemLine::RIGHT]) + aLinesState.rBoxInfoItem.SetValid(SvxBoxInfoItemValidFlags::RIGHT); + if (!aLinesState.aInnerLineIndeterminate[SvxBoxInfoItemLine::HORI]) + aLinesState.rBoxInfoItem.SetValid(SvxBoxInfoItemValidFlags::HORI); + if (!aLinesState.aInnerLineIndeterminate[SvxBoxInfoItemLine::VERT]) + aLinesState.rBoxInfoItem.SetValid(SvxBoxInfoItemValidFlags::VERT); + + if (!aLinesState.bDistanceIndeterminate) + { + if (aLinesState.aDistanceSet[SvxBoxItemLine::TOP]) + aLinesState.rBoxItem.SetDistance(aLinesState.aDistance[SvxBoxItemLine::TOP], SvxBoxItemLine::TOP); + if (aLinesState.aDistanceSet[SvxBoxItemLine::BOTTOM]) + aLinesState.rBoxItem.SetDistance(aLinesState.aDistance[SvxBoxItemLine::BOTTOM], SvxBoxItemLine::BOTTOM); + if (aLinesState.aDistanceSet[SvxBoxItemLine::LEFT]) + aLinesState.rBoxItem.SetDistance(aLinesState.aDistance[SvxBoxItemLine::LEFT], SvxBoxItemLine::LEFT); + if (aLinesState.aDistanceSet[SvxBoxItemLine::RIGHT]) + aLinesState.rBoxItem.SetDistance(aLinesState.aDistance[SvxBoxItemLine::RIGHT], SvxBoxItemLine::RIGHT); + aLinesState.rBoxInfoItem.SetValid(SvxBoxInfoItemValidFlags::DISTANCE); + } +} + +bool SvxTableController::selectRow( sal_Int32 row ) +{ + if( !mxTable.is() ) + return false; + CellPos aStart( 0, row ), aEnd( mxTable->getColumnCount() - 1, row ); + StartSelection( aEnd ); + gotoCell( aStart, true, nullptr ); + return true; +} + +bool SvxTableController::selectColumn( sal_Int32 column ) +{ + if( !mxTable.is() ) + return false; + CellPos aStart( column, 0 ), aEnd( column, mxTable->getRowCount() - 1 ); + StartSelection( aEnd ); + gotoCell( aStart, true, nullptr ); + return true; +} + +bool SvxTableController::deselectRow( sal_Int32 row ) +{ + if( !mxTable.is() ) + return false; + CellPos aStart( 0, row ), aEnd( mxTable->getColumnCount() - 1, row ); + StartSelection( aEnd ); + gotoCell( aStart, false, nullptr ); + return true; +} + +bool SvxTableController::deselectColumn( sal_Int32 column ) +{ + if( !mxTable.is() ) + return false; + CellPos aStart( column, 0 ), aEnd( column, mxTable->getRowCount() - 1 ); + StartSelection( aEnd ); + gotoCell( aStart, false, nullptr ); + return true; +} + +bool SvxTableController::isRowSelected( sal_Int32 nRow ) +{ + if( hasSelectedCells() ) + { + CellPos aFirstPos, aLastPos; + getSelectedCells( aFirstPos, aLastPos ); + if( (aFirstPos.mnCol == 0) && (nRow >= aFirstPos.mnRow && nRow <= aLastPos.mnRow) && (mxTable->getColumnCount() - 1 == aLastPos.mnCol) ) + return true; + } + return false; +} + +bool SvxTableController::isColumnSelected( sal_Int32 nColumn ) +{ + if( hasSelectedCells() ) + { + CellPos aFirstPos, aLastPos; + getSelectedCells( aFirstPos, aLastPos ); + if( (aFirstPos.mnRow == 0) && (nColumn >= aFirstPos.mnCol && nColumn <= aLastPos.mnCol) && (mxTable->getRowCount() - 1 == aLastPos.mnRow) ) + return true; + } + return false; +} + +bool SvxTableController::isRowHeader() +{ + if(!checkTableObject()) + return false; + + SdrTableObj& rTableObj(*mxTableObj); + TableStyleSettings aSettings(rTableObj.getTableStyleSettings()); + + return aSettings.mbUseFirstRow; +} + +bool SvxTableController::isColumnHeader() +{ + if(!checkTableObject()) + return false; + + SdrTableObj& rTableObj(*mxTableObj); + TableStyleSettings aSettings(rTableObj.getTableStyleSettings()); + + return aSettings.mbUseFirstColumn; +} + +bool SvxTableController::setCursorLogicPosition(const Point& rPosition, bool bPoint) +{ + if (mxTableObj->GetObjIdentifier() != SdrObjKind::Table) + return false; + + SdrTableObj* pTableObj = mxTableObj.get(); + CellPos aCellPos; + if (pTableObj->CheckTableHit(rPosition, aCellPos.mnCol, aCellPos.mnRow) != TableHitKind::NONE) + { + // Position is a table cell. + if (mbCellSelectionMode) + { + // We have a table selection already: adjust the point or the mark. + if (bPoint) + setSelectedCells(maCursorFirstPos, aCellPos); + else + setSelectedCells(aCellPos, maCursorLastPos); + return true; + } + else if (aCellPos != maMouseDownPos) + { + // No selection, but rPosition is at another cell: start table selection. + StartSelection(maMouseDownPos); + // Update graphic selection, should be hidden now. + mrView.AdjustMarkHdl(); + } + } + + return false; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3