/* -*- 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 { class SvxTableControllerModifyListener : public ::cppu::WeakImplHelper< css::util::XModifyListener > { public: explicit SvxTableControllerModifyListener( SvxTableController* pController ) : mpController( pController ) {} // XModifyListener virtual void SAL_CALL modified( const css::lang::EventObject& aEvent ) override; // XEventListener virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; SvxTableController* mpController; }; // XModifyListener void SAL_CALL SvxTableControllerModifyListener::modified( const css::lang::EventObject& ) { if( mpController ) mpController->onTableModified(); } // XEventListener void SAL_CALL SvxTableControllerModifyListener::disposing( const css::lang::EventObject& ) { mpController = nullptr; } rtl::Reference< sdr::SelectionController > CreateTableController( SdrView& rView, const SdrTableObj& rObj, const rtl::Reference< sdr::SelectionController >& xRefController ) { return SvxTableController::create(rView, rObj, xRefController); } rtl::Reference< sdr::SelectionController > SvxTableController::create( SdrView& rView, const SdrTableObj& rObj, const rtl::Reference< sdr::SelectionController >& xRefController ) { if( xRefController.is() ) { SvxTableController* pController = dynamic_cast< SvxTableController* >( xRefController.get() ); if(pController && (pController->mxTableObj.get() == &rObj) && (&pController->mrView == &rView)) { return xRefController; } } return new SvxTableController(rView, rObj); } SvxTableController::SvxTableController( SdrView& rView, const SdrTableObj& rObj) : mbCellSelectionMode(false) ,mbHasJustMerged(false) ,mbLeftButtonDown(false) ,mrView(rView) ,mxTableObj(const_cast< SdrTableObj* >(&rObj)) ,mnUpdateEvent( nullptr ) { rObj.getActiveCellPos( maCursorFirstPos ); maCursorLastPos = maCursorFirstPos; mxTable = mxTableObj.get()->getUnoTable(); if( mxTable ) { mxModifyListener = new SvxTableControllerModifyListener( this ); mxTable->addModifyListener( mxModifyListener ); } } SvxTableController::~SvxTableController() { if( mnUpdateEvent ) { Application::RemoveUserEvent( mnUpdateEvent ); } if( mxModifyListener.is() && mxTableObj.get() ) { rtl::Reference< TableModel > xTable( mxTableObj.get()->getUnoTable() ); if( xTable.is() ) { xTable->removeModifyListener( mxModifyListener ); mxModifyListener.clear(); } } } bool SvxTableController::onKeyInput(const KeyEvent& rKEvt, vcl::Window* pWindow ) { if(!checkTableObject()) return false; SdrTableObj& rTableObj(*mxTableObj.get()); SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); // check if we are read only if( rModel.IsReadOnly()) { switch( rKEvt.GetKeyCode().GetCode() ) { case awt::Key::DOWN: case awt::Key::UP: case awt::Key::LEFT: case awt::Key::RIGHT: case awt::Key::TAB: case awt::Key::HOME: case awt::Key::END: case awt::Key::NUM2: case awt::Key::NUM4: case awt::Key::NUM6: case awt::Key::NUM8: case awt::Key::ESCAPE: case awt::Key::F2: break; default: // tell the view we eat the event, no further processing needed return true; } } TblAction nAction = getKeyboardAction(rKEvt); return executeAction( nAction, rKEvt.GetKeyCode().IsShift(), pWindow ); } namespace { Point pixelToLogic(const Point& rPoint, vcl::Window const * pWindow) { if (!pWindow) return rPoint; return pWindow->PixelToLogic(rPoint); } } bool SvxTableController::onMouseButtonDown(const MouseEvent& rMEvt, vcl::Window* pWindow ) { if (comphelper::LibreOfficeKit::isActive() && !pWindow) { // Tiled rendering: get the window that has the disabled map mode. if (OutputDevice* pOutputDevice = mrView.GetFirstOutputDevice()) { if (pOutputDevice->GetOutDevType() == OUTDEV_WINDOW) pWindow = pOutputDevice->GetOwnerWindow(); } } if( !pWindow || !checkTableObject() ) return false; SdrViewEvent aVEvt; if( !rMEvt.IsRight() && mrView.PickAnything(rMEvt,SdrMouseEventKind::BUTTONDOWN, aVEvt) == SdrHitKind::Handle ) return false; TableHitKind eHit = mxTableObj.get()->CheckTableHit(pixelToLogic(rMEvt.GetPosPixel(), pWindow), maMouseDownPos.mnCol, maMouseDownPos.mnRow); mbLeftButtonDown = (rMEvt.GetClicks() == 1) && rMEvt.IsLeft(); if( eHit == TableHitKind::Cell ) { StartSelection( maMouseDownPos ); return true; } if( rMEvt.IsRight() && eHit != TableHitKind::NONE ) 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 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 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 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 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 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(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 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(TextDistancesToSvxBoxItem(aNewAttr))); auto xBoxInfoItem(std::make_shared(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 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(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(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 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.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(*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(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.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(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 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 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 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::NotFound(); } 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.get()); 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(mxTable->getCell(nCol, nRow)); 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; rtl::Reference 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()); pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, aSelection.toString()); } } 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"_ostr); pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_START, "EMPTY"_ostr); pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_END, "EMPTY"_ostr); pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, "EMPTY"_ostr); } } } 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( mxTable->getCell( nCol, nRow ) ); 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::INVALID == nState) rAttr.InvalidateItem(nWhich); else rAttr.MergeValue(rSet.Get(nWhich)); } else if(SfxItemState::SET == nState) { const SfxPoolItem& rItem = rSet.Get(nWhich); rAttr.MergeValue(rItem); } 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( mxTable->getCell( nCol, nRow ) ); 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() { rtl::Reference 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.get()); 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( mxTable->getCell( nCol, nRow ) ); 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.get().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; } rtl::Reference SvxTableController::GetMarkedSdrObjClone(SdrModel& rTargetModel) { rtl::Reference pRetval; sdr::table::SdrTableObj* pCurrentSdrTableObj(GetTableObj()); if(nullptr == pCurrentSdrTableObj) { return pRetval; } if(!mxTableObj.get().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 = SdrObject::Clone(*pCurrentSdrTableObj, rTargetModel); // limit SdrObject's TableModel to partial selection pRetval->CropTableModelToSelection(aStart, aEnd); } return pRetval; } bool SvxTableController::PasteObjModel( const SdrModel& rModel ) { if( mxTableObj.get().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; rtl::Reference< TableModel > xPasteTable( pPasteTableObj->getUnoTable() ); 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( mxTable->getCell( nTargetCol, aStart.mnRow + nRow ) ); if( xTargetCell.is() && !xTargetCell->isMerged() ) { CellRef xSourceCell(xPasteTable->getCell(nCol, nRow)); 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, sal_Int16 nDepth, bool bNoCharacterFormats, bool bNoParagraphFormats) { if(!mbCellSelectionMode) { return false; } if(!checkTableObject()) return false; SdrTableObj& rTableObj(*mxTableObj.get()); 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( mxTable->getCell( nCol, nRow ) ); if( xCell.is() ) { if (bUndo) xCell->AddUndo(); SdrText* pText = xCell.get(); SdrObjEditView::ApplyFormatPaintBrushToText( rFormatSet, rTableObj, pText, nDepth, 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( mxTable->getCell( nCol, nRow ) ); 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(TextDistancesToSvxBoxItem(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.get()); TableStyleSettings aSettings(rTableObj.getTableStyleSettings()); return aSettings.mbUseFirstRow; } bool SvxTableController::isColumnHeader() { if(!checkTableObject()) return false; SdrTableObj& rTableObj(*mxTableObj.get()); TableStyleSettings aSettings(rTableObj.getTableStyleSettings()); return aSettings.mbUseFirstColumn; } bool SvxTableController::setCursorLogicPosition(const Point& rPosition, bool bPoint) { rtl::Reference pTableObj = mxTableObj.get(); if (pTableObj->GetObjIdentifier() != SdrObjKind::Table) return false; 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: */