1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
22 #include <com/sun/star/lang/NoSupportException.hpp>
23 #include <svx/svdotable.hxx>
24 #include "cellcursor.hxx"
25 #include "tablelayouter.hxx"
27 #include <svx/svdmodel.hxx>
28 #include <svx/strings.hrc>
29 #include <svx/dialmgr.hxx>
30 #include <tools/debug.hxx>
31 #include <comphelper/diagnose_ex.hxx>
34 using namespace ::com::sun::star::uno
;
35 using namespace ::com::sun::star::lang
;
36 using namespace ::com::sun::star::container
;
37 using namespace ::com::sun::star::beans
;
38 using namespace ::com::sun::star::table
;
41 namespace sdr::table
{
43 CellCursor::CellCursor( const TableModelRef
& xTable
, sal_Int32 nLeft
, sal_Int32 nTop
, sal_Int32 nRight
, sal_Int32 nBottom
)
44 : CellCursorBase( xTable
, nLeft
, nTop
, nRight
, nBottom
)
49 CellCursor::~CellCursor()
57 Reference
< XCell
> SAL_CALL
CellCursor::getCellByPosition( sal_Int32 nColumn
, sal_Int32 nRow
)
59 return CellRange::getCellByPosition( nColumn
, nRow
);
63 Reference
< XCellRange
> SAL_CALL
CellCursor::getCellRangeByPosition( sal_Int32 nLeft
, sal_Int32 nTop
, sal_Int32 nRight
, sal_Int32 nBottom
)
65 return CellRange::getCellRangeByPosition( nLeft
, nTop
, nRight
, nBottom
);
69 Reference
< XCellRange
> SAL_CALL
CellCursor::getCellRangeByName( const OUString
& aRange
)
71 return CellRange::getCellRangeByName( aRange
);
78 void SAL_CALL
CellCursor::gotoStart( )
85 void SAL_CALL
CellCursor::gotoEnd( )
92 void SAL_CALL
CellCursor::gotoNext( )
97 if( mnRight
>= mxTable
->getColumnCount() )
99 // if we past the last column, try skip to the row line
101 if( mnTop
>= mxTable
->getRowCount() )
103 // if we past the last row, do not move cursor at all
109 // restart at the first column on the next row
120 void SAL_CALL
CellCursor::gotoPrevious( )
131 mnLeft
= mxTable
->getColumnCount() - 1;
140 void SAL_CALL
CellCursor::gotoOffset( ::sal_Int32 nColumnOffset
, ::sal_Int32 nRowOffset
)
144 const sal_Int32 nLeft
= mnLeft
+ nColumnOffset
;
145 if( (nLeft
>= 0) && (nLeft
< mxTable
->getColumnCount() ) )
146 mnRight
= mnLeft
= nLeft
;
148 const sal_Int32 nTop
= mnTop
+ nRowOffset
;
149 if( (nTop
>= 0) && (nTop
< mxTable
->getRowCount()) )
150 mnTop
= mnBottom
= nTop
;
155 // XMergeableCellCursor
158 /** returns true and the merged cell positions if a merge is valid or false if a merge is
159 not valid for that range */
160 bool CellCursor::GetMergedSelection( CellPos
& rStart
, CellPos
& rEnd
)
162 rStart
.mnCol
= mnLeft
; rStart
.mnRow
= mnTop
;
163 rEnd
.mnCol
= mnRight
; rEnd
.mnRow
= mnBottom
;
165 // single cell merge is never valid
166 if( mxTable
.is() && ((mnLeft
!= mnRight
) || (mnTop
!= mnBottom
)) ) try
168 CellRef xCell
= mxTable
->getCell( mnLeft
, mnTop
);
170 // check if first cell is merged
171 if( xCell
.is() && xCell
->isMerged() )
172 findMergeOrigin( mxTable
, mnLeft
, mnTop
, rStart
.mnCol
, rStart
.mnRow
);
174 // check if last cell is merged
175 xCell
= mxTable
->getCell( mnRight
, mnBottom
);
178 if( xCell
->isMerged() )
180 findMergeOrigin( mxTable
, mnRight
, mnBottom
, rEnd
.mnCol
, rEnd
.mnRow
);
181 // merge not possible if selection is only one cell and all its merges
184 xCell
= mxTable
->getCell( rEnd
.mnCol
, rEnd
.mnRow
);
189 rEnd
.mnCol
+= xCell
->getColumnSpan()-1;
190 rEnd
.mnRow
+= xCell
->getRowSpan()-1;
193 // now check if everything is inside the given bounds
194 sal_Int32 nRow
, nCol
;
195 for( nRow
= rStart
.mnRow
; nRow
<= rEnd
.mnRow
; nRow
++ )
197 for( nCol
= rStart
.mnCol
; nCol
<= rEnd
.mnCol
; nCol
++ )
199 xCell
= mxTable
->getCell( nCol
, nRow
);
203 if( xCell
->isMerged() )
205 sal_Int32 nOriginCol
, nOriginRow
;
206 if( findMergeOrigin( mxTable
, nCol
, nRow
, nOriginCol
, nOriginRow
) )
208 if( (nOriginCol
< rStart
.mnCol
) || (nOriginRow
< rStart
.mnRow
) )
211 xCell
= mxTable
->getCell( nOriginCol
, nOriginRow
);
214 nOriginCol
+= xCell
->getColumnSpan()-1;
215 nOriginRow
+= xCell
->getRowSpan()-1;
217 if( (nOriginCol
> rEnd
.mnCol
) || (nOriginRow
> rEnd
.mnRow
) )
222 else if( ((nCol
+ xCell
->getColumnSpan() - 1) > rEnd
.mnCol
) || ((nRow
+ xCell
->getRowSpan() - 1 ) > rEnd
.mnRow
) )
232 TOOLS_WARN_EXCEPTION("svx.table", "");
238 void SAL_CALL
CellCursor::merge( )
240 CellPos aStart
, aEnd
;
241 if( !GetMergedSelection( aStart
, aEnd
) )
242 throw NoSupportException();
244 if( !mxTable
.is() || (mxTable
->getSdrTableObj() == nullptr) )
245 throw DisposedException();
247 SdrModel
& rModel(mxTable
->getSdrTableObj()->getSdrModelFromSdrObject());
248 const bool bUndo(mxTable
->getSdrTableObj()->IsInserted() && rModel
.IsUndoEnabled());
251 rModel
.BegUndo( SvxResId(STR_TABLE_MERGE
) );
255 mxTable
->merge( aStart
.mnCol
, aStart
.mnRow
, aEnd
.mnCol
- aStart
.mnCol
+ 1, aEnd
.mnRow
- aStart
.mnRow
+ 1 );
257 mxTable
->setModified(true);
261 TOOLS_WARN_EXCEPTION("svx.table", "");
271 void CellCursor::split_column( sal_Int32 nCol
, sal_Int32 nColumns
, std::vector
< sal_Int32
>& rLeftOvers
)
273 const sal_Int32 nRowCount
= mxTable
->getRowCount();
275 sal_Int32 nNewCols
= 0, nRow
;
277 // first check how many columns we need to add
278 for( nRow
= mnTop
; nRow
<= mnBottom
; ++nRow
)
280 CellRef xCell
= mxTable
->getCell( nCol
, nRow
);
281 if( xCell
.is() && !xCell
->isMerged() )
282 nNewCols
= std::max( nNewCols
, nColumns
- xCell
->getColumnSpan() + 1 - rLeftOvers
[nRow
] );
287 static constexpr OUString
sWidth(u
"Width"_ustr
);
288 Reference
< XTableColumns
> xCols( mxTable
->getColumns(), UNO_SET_THROW
);
289 Reference
< XPropertySet
> xRefColumn( xCols
->getByIndex( nCol
), UNO_QUERY_THROW
);
290 sal_Int32 nWidth
= 0;
291 xRefColumn
->getPropertyValue( sWidth
) >>= nWidth
;
292 const sal_Int32 nNewWidth
= nWidth
/ (nNewCols
+ 1);
294 // reference column gets new width + rounding errors
295 xRefColumn
->setPropertyValue( sWidth
, Any( nWidth
- (nNewWidth
* nNewCols
) ) );
297 xCols
->insertByIndex( nCol
+ 1, nNewCols
);
300 // distribute new width
301 for( sal_Int32 nNewCol
= nCol
+ nNewCols
; nNewCol
> nCol
; --nNewCol
)
303 Reference
< XPropertySet
> xNewCol( xCols
->getByIndex( nNewCol
), UNO_QUERY_THROW
);
304 xNewCol
->setPropertyValue( sWidth
, Any( nNewWidth
) );
308 for( nRow
= 0; nRow
< nRowCount
; ++nRow
)
310 CellRef xCell
= mxTable
->getCell( nCol
, nRow
);
311 if( !xCell
.is() || xCell
->isMerged() )
315 // merged cells are ignored, but newly added columns will be added to leftovers
316 xCell
= mxTable
->getCell( nCol
+1, nRow
);
317 if( !xCell
.is() || !xCell
->isMerged() )
318 rLeftOvers
[nRow
] += nNewCols
;
323 sal_Int32 nRowSpan
= xCell
->getRowSpan() - 1;
324 sal_Int32 nColSpan
= xCell
->getColumnSpan() - 1;
326 if( (nRow
>= mnTop
) && (nRow
<= mnBottom
) )
328 sal_Int32 nCellsAvailable
= 1 + nColSpan
+ rLeftOvers
[nRow
];
330 nCellsAvailable
+= nNewCols
;
332 DBG_ASSERT( nCellsAvailable
> nColumns
, "sdr::table::CellCursor::split_column(), somethings wrong" );
334 sal_Int32 nSplitSpan
= (nCellsAvailable
/ (nColumns
+ 1)) - 1;
336 sal_Int32 nSplitCol
= nCol
;
337 sal_Int32 nSplits
= nColumns
+ 1;
340 // last split eats rounding cells
342 nSplitSpan
= nCellsAvailable
- ((nSplitSpan
+1) * nColumns
) - 1;
344 mxTable
->merge( nSplitCol
, nRow
, nSplitSpan
+ 1, nRowSpan
+ 1);
346 nSplitCol
+= nSplitSpan
+ 1;
351 rLeftOvers
[nRow
++] = 0;
358 // cope with outside cells, merge if needed
359 if( nColSpan
< (rLeftOvers
[nRow
] + nNewCols
) )
360 mxTable
->merge( nCol
, nRow
, (rLeftOvers
[nRow
] + nNewCols
) + 1, nRowSpan
+ 1 );
364 rLeftOvers
[nRow
++] = 0; // consumed
374 void CellCursor::split_horizontal( sal_Int32 nColumns
)
376 const sal_Int32 nRowCount
= mxTable
->getRowCount();
378 std::vector
< sal_Int32
> aLeftOvers( nRowCount
);
380 for( sal_Int32 nCol
= mnRight
; nCol
>= mnLeft
; --nCol
)
381 split_column( nCol
, nColumns
, aLeftOvers
);
385 void CellCursor::split_row( sal_Int32 nRow
, sal_Int32 nRows
, std::vector
< sal_Int32
>& rLeftOvers
)
387 const sal_Int32 nColCount
= mxTable
->getColumnCount();
389 sal_Int32 nNewRows
= 0, nCol
;
391 // first check how many columns we need to add
392 for( nCol
= mnLeft
; nCol
<= mnRight
; ++nCol
)
394 CellRef xCell
= mxTable
->getCell( nCol
, nRow
);
395 if( xCell
.is() && !xCell
->isMerged() )
396 nNewRows
= std::max( nNewRows
, nRows
- xCell
->getRowSpan() + 1 - rLeftOvers
[nCol
] );
401 static constexpr OUString
sHeight(u
"Height"_ustr
);
402 Reference
< XTableRows
> xRows( mxTable
->getRows(), UNO_SET_THROW
);
403 Reference
< XPropertySet
> xRefRow( xRows
->getByIndex( nRow
), UNO_QUERY_THROW
);
404 sal_Int32 nHeight
= 0;
405 xRefRow
->getPropertyValue( sHeight
) >>= nHeight
;
406 const sal_Int32 nNewHeight
= nHeight
/ (nNewRows
+ 1);
408 // reference row gets new height + rounding errors
409 xRefRow
->setPropertyValue( sHeight
, Any( nHeight
- (nNewHeight
* nNewRows
) ) );
411 xRows
->insertByIndex( nRow
+ 1, nNewRows
);
412 mnBottom
+= nNewRows
;
414 // distribute new width
415 for( sal_Int32 nNewRow
= nRow
+ nNewRows
; nNewRow
> nRow
; --nNewRow
)
417 Reference
< XPropertySet
> xNewRow( xRows
->getByIndex( nNewRow
), UNO_QUERY_THROW
);
418 xNewRow
->setPropertyValue( sHeight
, Any( nNewHeight
) );
422 for( nCol
= 0; nCol
< nColCount
; ++nCol
)
424 CellRef xCell
= mxTable
->getCell( nCol
, nRow
);
425 if( !xCell
.is() || xCell
->isMerged() )
429 // merged cells are ignored, but newly added columns will be added to leftovers
430 xCell
= mxTable
->getCell( nCol
, nRow
+1 );
431 if( !xCell
.is() || !xCell
->isMerged() )
432 rLeftOvers
[nCol
] += nNewRows
;
437 sal_Int32 nRowSpan
= xCell
->getRowSpan() - 1;
438 sal_Int32 nColSpan
= xCell
->getColumnSpan() - 1;
440 if( (nCol
>= mnLeft
) && (nCol
<= mnRight
) )
442 sal_Int32 nCellsAvailable
= 1 + nRowSpan
+ rLeftOvers
[nCol
];
444 nCellsAvailable
+= nNewRows
;
446 DBG_ASSERT( nCellsAvailable
> nRows
, "sdr::table::CellCursor::split_row(), somethings wrong" );
448 sal_Int32 nSplitSpan
= (nCellsAvailable
/ (nRows
+ 1)) - 1;
450 sal_Int32 nSplitRow
= nRow
;
451 sal_Int32 nSplits
= nRows
+ 1;
454 // last split eats rounding cells
456 nSplitSpan
= nCellsAvailable
- ((nSplitSpan
+1) * nRows
) - 1;
458 mxTable
->merge( nCol
, nSplitRow
, nColSpan
+ 1, nSplitSpan
+ 1 );
460 nSplitRow
+= nSplitSpan
+ 1;
465 rLeftOvers
[nCol
++] = 0;
472 // cope with outside cells, merge if needed
473 if( nRowSpan
< (rLeftOvers
[nCol
] + nNewRows
) )
474 mxTable
->merge( nCol
, nRow
, nColSpan
+ 1, (rLeftOvers
[nCol
] + nNewRows
) + 1 );
478 rLeftOvers
[nCol
++] = 0; // consumed
488 void CellCursor::split_vertical( sal_Int32 nRows
)
490 const sal_Int32 nColCount
= mxTable
->getColumnCount();
492 std::vector
< sal_Int32
> aLeftOvers( nColCount
);
494 for( sal_Int32 nRow
= mnBottom
; nRow
>= mnTop
; --nRow
)
495 split_row( nRow
, nRows
, aLeftOvers
);
499 void SAL_CALL
CellCursor::split( sal_Int32 nColumns
, sal_Int32 nRows
)
501 if( (nColumns
< 0) || (nRows
< 0) )
502 throw IllegalArgumentException();
504 if( !mxTable
.is() || (mxTable
->getSdrTableObj() == nullptr) )
505 throw DisposedException();
507 SdrModel
& rModel(mxTable
->getSdrTableObj()->getSdrModelFromSdrObject());
508 const bool bUndo(mxTable
->getSdrTableObj()->IsInserted() && rModel
.IsUndoEnabled());
511 rModel
.BegUndo( SvxResId(STR_TABLE_SPLIT
) );
516 split_horizontal( nColumns
);
519 split_vertical( nRows
);
521 if( nColumns
> 0 ||nRows
> 0 )
522 mxTable
->setModified(true);
526 TOOLS_WARN_EXCEPTION("svx.table", "");
527 throw NoSupportException();
537 sal_Bool SAL_CALL
CellCursor::isMergeable( )
539 CellPos aStart
, aEnd
;
540 return GetMergedSelection( aStart
, aEnd
);
546 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */