/* -*- 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 "cellcursor.hxx" #include "tablelayouter.hxx" #include #include #include #include #include #include using namespace ::com::sun::star::uno; using namespace ::com::sun::star::lang; using namespace ::com::sun::star::container; using namespace ::com::sun::star::beans; using namespace ::com::sun::star::table; namespace sdr::table { CellCursor::CellCursor( const TableModelRef & xTable, sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ) : CellCursorBase( xTable, nLeft, nTop, nRight, nBottom ) { } CellCursor::~CellCursor() { } // XCellCursor Reference< XCell > SAL_CALL CellCursor::getCellByPosition( sal_Int32 nColumn, sal_Int32 nRow ) { return CellRange::getCellByPosition( nColumn, nRow ); } Reference< XCellRange > SAL_CALL CellCursor::getCellRangeByPosition( sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ) { return CellRange::getCellRangeByPosition( nLeft, nTop, nRight, nBottom ); } Reference< XCellRange > SAL_CALL CellCursor::getCellRangeByName( const OUString& aRange ) { return CellRange::getCellRangeByName( aRange ); } // XCellCursor void SAL_CALL CellCursor::gotoStart( ) { mnRight = mnLeft; mnBottom = mnTop; } void SAL_CALL CellCursor::gotoEnd( ) { mnLeft = mnRight; mnTop = mnBottom; } void SAL_CALL CellCursor::gotoNext( ) { if( mxTable.is() ) { mnRight++; if( mnRight >= mxTable->getColumnCount() ) { // if we past the last column, try skip to the row line mnTop++; if( mnTop >= mxTable->getRowCount() ) { // if we past the last row, do not move cursor at all mnTop--; mnRight--; } else { // restart at the first column on the next row mnRight = 0; } } } mnLeft = mnRight; mnTop = mnBottom; } void SAL_CALL CellCursor::gotoPrevious( ) { if( mxTable.is() ) { if( mnLeft > 0 ) { --mnLeft; } else if( mnTop > 0 ) { --mnTop; mnLeft = mxTable->getColumnCount() - 1; } } mnRight = mnLeft; mnBottom = mnTop; } void SAL_CALL CellCursor::gotoOffset( ::sal_Int32 nColumnOffset, ::sal_Int32 nRowOffset ) { if( mxTable.is() ) { const sal_Int32 nLeft = mnLeft + nColumnOffset; if( (nLeft >= 0) && (nLeft < mxTable->getColumnCount() ) ) mnRight = mnLeft = nLeft; const sal_Int32 nTop = mnTop + nRowOffset; if( (nTop >= 0) && (nTop < mxTable->getRowCount()) ) mnTop = mnBottom = nTop; } } // XMergeableCellCursor /** returns true and the merged cell positions if a merge is valid or false if a merge is not valid for that range */ bool CellCursor::GetMergedSelection( CellPos& rStart, CellPos& rEnd ) { rStart.mnCol = mnLeft; rStart.mnRow = mnTop; rEnd.mnCol = mnRight; rEnd.mnRow = mnBottom; // single cell merge is never valid if( mxTable.is() && ((mnLeft != mnRight) || (mnTop != mnBottom)) ) try { CellRef xCell = mxTable->getCell( mnLeft, mnTop ); // check if first cell is merged if( xCell.is() && xCell->isMerged() ) findMergeOrigin( mxTable, mnLeft, mnTop, rStart.mnCol, rStart.mnRow ); // check if last cell is merged xCell = mxTable->getCell( mnRight, mnBottom ); if( xCell.is() ) { if( xCell->isMerged() ) { findMergeOrigin( mxTable, mnRight, mnBottom, rEnd.mnCol, rEnd.mnRow ); // merge not possible if selection is only one cell and all its merges if( rEnd == rStart ) return false; xCell = mxTable->getCell( rEnd.mnCol, rEnd.mnRow ); } } if( xCell.is() ) { rEnd.mnCol += xCell->getColumnSpan()-1; rEnd.mnRow += xCell->getRowSpan()-1; } // now check if everything is inside the given bounds sal_Int32 nRow, nCol; for( nRow = rStart.mnRow; nRow <= rEnd.mnRow; nRow++ ) { for( nCol = rStart.mnCol; nCol <= rEnd.mnCol; nCol++ ) { xCell = mxTable->getCell( nCol, nRow ); if( !xCell.is() ) continue; if( xCell->isMerged() ) { sal_Int32 nOriginCol, nOriginRow; if( findMergeOrigin( mxTable, nCol, nRow, nOriginCol, nOriginRow ) ) { if( (nOriginCol < rStart.mnCol) || (nOriginRow < rStart.mnRow) ) return false; xCell = mxTable->getCell( nOriginCol, nOriginRow ); if( xCell.is() ) { nOriginCol += xCell->getColumnSpan()-1; nOriginRow += xCell->getRowSpan()-1; if( (nOriginCol > rEnd.mnCol) || (nOriginRow > rEnd.mnRow) ) return false; } } } else if( ((nCol + xCell->getColumnSpan() - 1) > rEnd.mnCol) || ((nRow + xCell->getRowSpan() - 1 ) > rEnd.mnRow) ) { return false; } } } return true; } catch( Exception& ) { TOOLS_WARN_EXCEPTION("svx.table", ""); } return false; } void SAL_CALL CellCursor::merge( ) { CellPos aStart, aEnd; if( !GetMergedSelection( aStart, aEnd ) ) throw NoSupportException(); if( !mxTable.is() || (mxTable->getSdrTableObj() == nullptr) ) throw DisposedException(); SdrModel& rModel(mxTable->getSdrTableObj()->getSdrModelFromSdrObject()); const bool bUndo(mxTable->getSdrTableObj()->IsInserted() && rModel.IsUndoEnabled()); if( bUndo ) rModel.BegUndo( SvxResId(STR_TABLE_MERGE) ); try { mxTable->merge( aStart.mnCol, aStart.mnRow, aEnd.mnCol - aStart.mnCol + 1, aEnd.mnRow - aStart.mnRow + 1 ); mxTable->optimize(); mxTable->setModified(true); } catch( Exception& ) { TOOLS_WARN_EXCEPTION("svx.table", ""); } if( bUndo ) rModel.EndUndo(); rModel.SetChanged(); } void CellCursor::split_column( sal_Int32 nCol, sal_Int32 nColumns, std::vector< sal_Int32 >& rLeftOvers ) { const sal_Int32 nRowCount = mxTable->getRowCount(); sal_Int32 nNewCols = 0, nRow; // first check how many columns we need to add for( nRow = mnTop; nRow <= mnBottom; ++nRow ) { CellRef xCell = mxTable->getCell( nCol, nRow ); if( xCell.is() && !xCell->isMerged() ) nNewCols = std::max( nNewCols, nColumns - xCell->getColumnSpan() + 1 - rLeftOvers[nRow] ); } if( nNewCols > 0 ) { static constexpr OUString sWidth(u"Width"_ustr); Reference< XTableColumns > xCols( mxTable->getColumns(), UNO_SET_THROW ); Reference< XPropertySet > xRefColumn( xCols->getByIndex( nCol ), UNO_QUERY_THROW ); sal_Int32 nWidth = 0; xRefColumn->getPropertyValue( sWidth ) >>= nWidth; const sal_Int32 nNewWidth = nWidth / (nNewCols + 1); // reference column gets new width + rounding errors xRefColumn->setPropertyValue( sWidth, Any( nWidth - (nNewWidth * nNewCols) ) ); xCols->insertByIndex( nCol + 1, nNewCols ); mnRight += nNewCols; // distribute new width for( sal_Int32 nNewCol = nCol + nNewCols; nNewCol > nCol; --nNewCol ) { Reference< XPropertySet > xNewCol( xCols->getByIndex( nNewCol ), UNO_QUERY_THROW ); xNewCol->setPropertyValue( sWidth, Any( nNewWidth ) ); } } for( nRow = 0; nRow < nRowCount; ++nRow ) { CellRef xCell = mxTable->getCell( nCol, nRow ); if( !xCell.is() || xCell->isMerged() ) { if( nNewCols > 0 ) { // merged cells are ignored, but newly added columns will be added to leftovers xCell = mxTable->getCell( nCol+1, nRow ); if( !xCell.is() || !xCell->isMerged() ) rLeftOvers[nRow] += nNewCols; } } else { sal_Int32 nRowSpan = xCell->getRowSpan() - 1; sal_Int32 nColSpan = xCell->getColumnSpan() - 1; if( (nRow >= mnTop) && (nRow <= mnBottom) ) { sal_Int32 nCellsAvailable = 1 + nColSpan + rLeftOvers[nRow]; if( nColSpan == 0 ) nCellsAvailable += nNewCols; DBG_ASSERT( nCellsAvailable > nColumns, "sdr::table::CellCursor::split_column(), somethings wrong" ); sal_Int32 nSplitSpan = (nCellsAvailable / (nColumns + 1)) - 1; sal_Int32 nSplitCol = nCol; sal_Int32 nSplits = nColumns + 1; while( nSplits-- ) { // last split eats rounding cells if( nSplits == 0 ) nSplitSpan = nCellsAvailable - ((nSplitSpan+1) * nColumns) - 1; mxTable->merge( nSplitCol, nRow, nSplitSpan + 1, nRowSpan + 1); if( nSplits > 0 ) nSplitCol += nSplitSpan + 1; } do { rLeftOvers[nRow++] = 0; } while( nRowSpan-- ); --nRow; } else { // cope with outside cells, merge if needed if( nColSpan < (rLeftOvers[nRow] + nNewCols) ) mxTable->merge( nCol, nRow, (rLeftOvers[nRow] + nNewCols) + 1, nRowSpan + 1 ); do { rLeftOvers[nRow++] = 0; // consumed } while( nRowSpan-- ); --nRow; } } } } void CellCursor::split_horizontal( sal_Int32 nColumns ) { const sal_Int32 nRowCount = mxTable->getRowCount(); std::vector< sal_Int32 > aLeftOvers( nRowCount ); for( sal_Int32 nCol = mnRight; nCol >= mnLeft; --nCol ) split_column( nCol, nColumns, aLeftOvers ); } void CellCursor::split_row( sal_Int32 nRow, sal_Int32 nRows, std::vector< sal_Int32 >& rLeftOvers ) { const sal_Int32 nColCount = mxTable->getColumnCount(); sal_Int32 nNewRows = 0, nCol; // first check how many columns we need to add for( nCol = mnLeft; nCol <= mnRight; ++nCol ) { CellRef xCell = mxTable->getCell( nCol, nRow ); if( xCell.is() && !xCell->isMerged() ) nNewRows = std::max( nNewRows, nRows - xCell->getRowSpan() + 1 - rLeftOvers[nCol] ); } if( nNewRows > 0 ) { static constexpr OUString sHeight(u"Height"_ustr); Reference< XTableRows > xRows( mxTable->getRows(), UNO_SET_THROW ); Reference< XPropertySet > xRefRow( xRows->getByIndex( nRow ), UNO_QUERY_THROW ); sal_Int32 nHeight = 0; xRefRow->getPropertyValue( sHeight ) >>= nHeight; const sal_Int32 nNewHeight = nHeight / (nNewRows + 1); // reference row gets new height + rounding errors xRefRow->setPropertyValue( sHeight, Any( nHeight - (nNewHeight * nNewRows) ) ); xRows->insertByIndex( nRow + 1, nNewRows ); mnBottom += nNewRows; // distribute new width for( sal_Int32 nNewRow = nRow + nNewRows; nNewRow > nRow; --nNewRow ) { Reference< XPropertySet > xNewRow( xRows->getByIndex( nNewRow ), UNO_QUERY_THROW ); xNewRow->setPropertyValue( sHeight, Any( nNewHeight ) ); } } for( nCol = 0; nCol < nColCount; ++nCol ) { CellRef xCell = mxTable->getCell( nCol, nRow ); if( !xCell.is() || xCell->isMerged() ) { if( nNewRows ) { // merged cells are ignored, but newly added columns will be added to leftovers xCell = mxTable->getCell( nCol, nRow+1 ); if( !xCell.is() || !xCell->isMerged() ) rLeftOvers[nCol] += nNewRows; } } else { sal_Int32 nRowSpan = xCell->getRowSpan() - 1; sal_Int32 nColSpan = xCell->getColumnSpan() - 1; if( (nCol >= mnLeft) && (nCol <= mnRight) ) { sal_Int32 nCellsAvailable = 1 + nRowSpan + rLeftOvers[nCol]; if( nRowSpan == 0 ) nCellsAvailable += nNewRows; DBG_ASSERT( nCellsAvailable > nRows, "sdr::table::CellCursor::split_row(), somethings wrong" ); sal_Int32 nSplitSpan = (nCellsAvailable / (nRows + 1)) - 1; sal_Int32 nSplitRow = nRow; sal_Int32 nSplits = nRows + 1; while( nSplits-- ) { // last split eats rounding cells if( nSplits == 0 ) nSplitSpan = nCellsAvailable - ((nSplitSpan+1) * nRows) - 1; mxTable->merge( nCol, nSplitRow, nColSpan + 1, nSplitSpan + 1 ); if( nSplits > 0 ) nSplitRow += nSplitSpan + 1; } do { rLeftOvers[nCol++] = 0; } while( nColSpan-- ); --nCol; } else { // cope with outside cells, merge if needed if( nRowSpan < (rLeftOvers[nCol] + nNewRows) ) mxTable->merge( nCol, nRow, nColSpan + 1, (rLeftOvers[nCol] + nNewRows) + 1 ); do { rLeftOvers[nCol++] = 0; // consumed } while( nColSpan-- ); --nCol; } } } } void CellCursor::split_vertical( sal_Int32 nRows ) { const sal_Int32 nColCount = mxTable->getColumnCount(); std::vector< sal_Int32 > aLeftOvers( nColCount ); for( sal_Int32 nRow = mnBottom; nRow >= mnTop; --nRow ) split_row( nRow, nRows, aLeftOvers ); } void SAL_CALL CellCursor::split( sal_Int32 nColumns, sal_Int32 nRows ) { if( (nColumns < 0) || (nRows < 0) ) throw IllegalArgumentException(); if( !mxTable.is() || (mxTable->getSdrTableObj() == nullptr) ) throw DisposedException(); SdrModel& rModel(mxTable->getSdrTableObj()->getSdrModelFromSdrObject()); const bool bUndo(mxTable->getSdrTableObj()->IsInserted() && rModel.IsUndoEnabled()); if( bUndo ) rModel.BegUndo( SvxResId(STR_TABLE_SPLIT) ); try { if( nColumns > 0 ) split_horizontal( nColumns ); if( nRows > 0 ) split_vertical( nRows ); if( nColumns > 0 ||nRows > 0 ) mxTable->setModified(true); } catch( Exception& ) { TOOLS_WARN_EXCEPTION("svx.table", ""); throw NoSupportException(); } if( bUndo ) rModel.EndUndo(); rModel.SetChanged(); } sal_Bool SAL_CALL CellCursor::isMergeable( ) { CellPos aStart, aEnd; return GetMergedSelection( aStart, aEnd ); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */