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 .
21 #include <com/sun/star/table/XMergeableCell.hpp>
23 #include <tools/debug.hxx>
24 #include <tools/gen.hxx>
25 #include <libxml/xmlwriter.h>
28 #include "cellrange.hxx"
29 #include <o3tl/safeint.hxx>
30 #include <tablemodel.hxx>
31 #include "tablerow.hxx"
32 #include "tablerows.hxx"
33 #include "tablecolumn.hxx"
34 #include "tablecolumns.hxx"
35 #include "tablelayouter.hxx"
36 #include <svx/svdotable.hxx>
37 #include <editeng/borderline.hxx>
38 #include <editeng/boxitem.hxx>
39 #include <svx/svdmodel.hxx>
41 using ::editeng::SvxBorderLine
;
42 using namespace ::com::sun::star::uno
;
43 using namespace ::com::sun::star::lang
;
44 using namespace ::com::sun::star::container
;
45 using namespace ::com::sun::star::beans
;
46 using namespace ::com::sun::star::table
;
47 using namespace ::com::sun::star::text
;
50 namespace sdr
{ namespace table
{
53 static SvxBorderLine gEmptyBorder
;
55 static const OUStringLiteral
gsSize( "Size" );
57 TableLayouter::TableLayouter( const TableModelRef
& xTableModel
)
58 : mxTable( xTableModel
)
63 TableLayouter::~TableLayouter()
69 basegfx::B2ITuple
TableLayouter::getCellSize( const CellRef
& xCell
, const CellPos
& rPos
) const
76 if( xCell
.is() && !xCell
->isMerged() )
80 sal_Int32 nRowCount
= getRowCount();
81 sal_Int32 nRowSpan
= std::max( xCell
->getRowSpan(), sal_Int32(1) );
82 while( nRowSpan
&& (aPos
.mnRow
< nRowCount
) )
84 if( static_cast<sal_Int32
>(maRows
.size()) <= aPos
.mnRow
)
87 height
= o3tl::saturating_add(height
, maRows
[aPos
.mnRow
++].mnSize
);
91 sal_Int32 nColCount
= getColumnCount();
92 sal_Int32 nColSpan
= std::max( xCell
->getColumnSpan(), sal_Int32(1) );
93 while( nColSpan
&& (aPos
.mnCol
< nColCount
) )
95 if( static_cast<sal_Int32
>(maColumns
.size()) <= aPos
.mnCol
)
98 width
= o3tl::saturating_add(width
, maColumns
[aPos
.mnCol
++].mnSize
);
105 OSL_FAIL( "TableLayouter::getCellSize(), exception caught!" );
108 return basegfx::B2ITuple( width
, height
);
112 bool TableLayouter::getCellArea( const CellRef
& xCell
, const CellPos
& rPos
, basegfx::B2IRectangle
& rArea
) const
116 if( xCell
.is() && !xCell
->isMerged() && isValid(rPos
) )
118 const basegfx::B2ITuple
aCellSize( getCellSize( xCell
, rPos
) );
119 const bool bRTL
= (mxTable
->getSdrTableObj()->GetWritingMode() == WritingMode_RL_TB
);
121 if( (rPos
.mnCol
< static_cast<sal_Int32
>(maColumns
.size())) && (rPos
.mnRow
< static_cast<sal_Int32
>(maRows
.size()) ) )
123 const sal_Int32 y
= maRows
[rPos
.mnRow
].mnPos
;
126 if (o3tl::checked_add(y
, aCellSize
.getY(), endy
))
131 ///For RTL Table Calculate the Right End of cell instead of Left
132 const sal_Int32 x
= maColumns
[rPos
.mnCol
].mnPos
+ maColumns
[rPos
.mnCol
].mnSize
;
134 if (o3tl::checked_sub(x
, aCellSize
.getX(), startx
))
136 rArea
= basegfx::B2IRectangle(startx
, y
, x
, endy
);
140 const sal_Int32 x
= maColumns
[rPos
.mnCol
].mnPos
;
142 if (o3tl::checked_add(x
, aCellSize
.getX(), endx
))
144 rArea
= basegfx::B2IRectangle(x
, y
, endx
, endy
);
152 OSL_FAIL( "TableLayouter::getCellSize(), exception caught!" );
158 sal_Int32
TableLayouter::getRowHeight( sal_Int32 nRow
) const
160 if( isValidRow(nRow
) )
161 return maRows
[nRow
].mnSize
;
167 sal_Int32
TableLayouter::getColumnWidth( sal_Int32 nColumn
) const
169 if( isValidColumn(nColumn
) )
170 return maColumns
[nColumn
].mnSize
;
175 sal_Int32
TableLayouter::calcPreferredColumnWidth( sal_Int32 nColumn
, Size aSize
) const
178 for ( sal_uInt32 nRow
= 0; nRow
< static_cast<sal_uInt32
>(maRows
.size()); ++nRow
)
180 // Account for the space desired by spanned columns.
181 // Only the last spanned cell will try to ensure sufficient space,
182 // by looping through the previous columns, subtracting their portion.
184 sal_Int32 nSpannedColumn
= nColumn
;
185 bool bFindSpan
= true;
186 while ( bFindSpan
&& isValidColumn(nSpannedColumn
) )
188 // recursive function call gets earlier portion of spanned column.
189 if ( nSpannedColumn
< nColumn
)
190 nWish
-= calcPreferredColumnWidth( nSpannedColumn
, aSize
);
192 CellRef
xCell( getCell( CellPos( nSpannedColumn
, nRow
) ) );
193 if ( xCell
.is() && !xCell
->isMerged()
194 && (xCell
->getColumnSpan() == 1 || nSpannedColumn
< nColumn
) )
196 nWish
+= xCell
->calcPreferredWidth(aSize
);
199 else if ( xCell
.is() && xCell
->isMerged()
200 && nColumn
== nSpannedColumn
201 && isValidColumn(nColumn
+ 1) )
203 xCell
= getCell( CellPos( nColumn
+ 1, nRow
) );
204 bFindSpan
= xCell
.is() && !xCell
->isMerged();
208 nRet
= std::max( nRet
, nWish
);
214 bool TableLayouter::isEdgeVisible( sal_Int32 nEdgeX
, sal_Int32 nEdgeY
, bool bHorizontal
) const
216 const BorderLineMap
& rMap
= bHorizontal
? maHorizontalBorders
: maVerticalBorders
;
218 if( (nEdgeX
>= 0) && (nEdgeX
< sal::static_int_cast
<sal_Int32
>(rMap
.size())) &&
219 (nEdgeY
>= 0) && (nEdgeY
< sal::static_int_cast
<sal_Int32
>(rMap
[nEdgeX
].size())) )
221 return rMap
[nEdgeX
][nEdgeY
] != nullptr;
225 OSL_FAIL( "sdr::table::TableLayouter::getBorderLine(), invalid edge!" );
232 /** returns the requested borderline in rpBorderLine or a null pointer if there is no border at this edge */
233 SvxBorderLine
* TableLayouter::getBorderLine( sal_Int32 nEdgeX
, sal_Int32 nEdgeY
, bool bHorizontal
)const
235 SvxBorderLine
* pLine
= nullptr;
237 const BorderLineMap
& rMap
= bHorizontal
? maHorizontalBorders
: maVerticalBorders
;
239 if( (nEdgeX
>= 0) && (nEdgeX
< sal::static_int_cast
<sal_Int32
>(rMap
.size())) &&
240 (nEdgeY
>= 0) && (nEdgeY
< sal::static_int_cast
<sal_Int32
>(rMap
[nEdgeX
].size())) )
242 pLine
= rMap
[nEdgeX
][nEdgeY
];
243 if( pLine
== &gEmptyBorder
)
248 OSL_FAIL( "sdr::table::TableLayouter::getBorderLine(), invalid edge!" );
254 std::vector
<EdgeInfo
> TableLayouter::getHorizontalEdges()
256 std::vector
<EdgeInfo
> aReturn
;
257 sal_Int32 nRowSize
= sal_Int32(maRows
.size());
258 for (sal_Int32 i
= 0; i
<= nRowSize
; i
++)
260 sal_Int32 nEdgeMin
= 0;
261 sal_Int32 nEdgeMax
= 0;
262 sal_Int32 nEdge
= getHorizontalEdge(i
, &nEdgeMin
, &nEdgeMax
);
265 aReturn
.emplace_back(i
, nEdge
, nEdgeMin
, nEdgeMax
);
270 std::vector
<EdgeInfo
> TableLayouter::getVerticalEdges()
272 std::vector
<EdgeInfo
> aReturn
;
273 sal_Int32 nColumnSize
= sal_Int32(maColumns
.size());
274 for (sal_Int32 i
= 0; i
<= nColumnSize
; i
++)
276 sal_Int32 nEdgeMin
= 0;
277 sal_Int32 nEdgeMax
= 0;
278 sal_Int32 nEdge
= getVerticalEdge(i
, &nEdgeMin
, &nEdgeMax
);
281 aReturn
.emplace_back(i
, nEdge
, nEdgeMin
, nEdgeMax
);
286 sal_Int32
TableLayouter::getHorizontalEdge( int nEdgeY
, sal_Int32
* pnMin
/*= 0*/, sal_Int32
* pnMax
/*= 0*/ )
289 const sal_Int32 nRowCount
= getRowCount();
290 if( (nEdgeY
>= 0) && (nEdgeY
<= nRowCount
) )
291 nRet
= maRows
[std::min(static_cast<sal_Int32
>(nEdgeY
),nRowCount
-1)].mnPos
;
293 if( nEdgeY
== nRowCount
)
294 nRet
+= maRows
[nEdgeY
- 1].mnSize
;
298 if( (nEdgeY
> 0) && (nEdgeY
<= nRowCount
) )
300 *pnMin
= maRows
[nEdgeY
-1].mnPos
+ 600; // todo
316 sal_Int32
TableLayouter::getVerticalEdge( int nEdgeX
, sal_Int32
* pnMin
/*= 0*/, sal_Int32
* pnMax
/*= 0*/ )
320 const sal_Int32 nColCount
= getColumnCount();
321 if( (nEdgeX
>= 0) && (nEdgeX
<= nColCount
) )
322 nRet
= maColumns
[std::min(static_cast<sal_Int32
>(nEdgeX
),nColCount
-1)].mnPos
;
324 const bool bRTL
= (mxTable
->getSdrTableObj()->GetWritingMode() == WritingMode_RL_TB
);
327 if( (nEdgeX
>= 0) && (nEdgeX
< nColCount
) )
328 nRet
+= maColumns
[nEdgeX
].mnSize
;
332 if( nEdgeX
== nColCount
)
333 nRet
+= maColumns
[nEdgeX
- 1].mnSize
;
341 if( nEdgeX
< nColCount
)
342 *pnMin
= nRet
- maColumns
[nEdgeX
].mnSize
+ getMinimumColumnWidth(nEdgeX
);
346 if( (nEdgeX
> 0) && (nEdgeX
<= nColCount
) )
347 *pnMin
= maColumns
[nEdgeX
-1].mnPos
+ getMinimumColumnWidth( nEdgeX
-1 );
353 *pnMax
= 0x0fffffff; // todo
357 *pnMax
= nRet
+ maColumns
[nEdgeX
-1].mnSize
- getMinimumColumnWidth( nEdgeX
-1 );
361 if( (nEdgeX
>= 0) && (nEdgeX
< nColCount
) )
362 *pnMax
= maColumns
[nEdgeX
].mnPos
+ maColumns
[nEdgeX
].mnSize
- getMinimumColumnWidth( nEdgeX
);
370 static bool checkMergeOrigin( const TableModelRef
& xTable
, sal_Int32 nMergedX
, sal_Int32 nMergedY
, sal_Int32 nCellX
, sal_Int32 nCellY
, bool& bRunning
)
372 Reference
< XMergeableCell
> xCell( xTable
->getCellByPosition( nCellX
, nCellY
), UNO_QUERY
);
373 if( xCell
.is() && !xCell
->isMerged() )
375 const sal_Int32 nRight
= xCell
->getColumnSpan() + nCellX
;
376 const sal_Int32 nBottom
= xCell
->getRowSpan() + nCellY
;
377 if( (nMergedX
< nRight
) && (nMergedY
< nBottom
) )
385 /** returns true if the cell(nMergedX,nMergedY) is merged with other cells.
386 the returned cell( rOriginX, rOriginY ) is the origin( top left cell ) of the merge.
388 bool findMergeOrigin( const TableModelRef
& xTable
, sal_Int32 nMergedX
, sal_Int32 nMergedY
, sal_Int32
& rOriginX
, sal_Int32
& rOriginY
)
393 if( xTable
.is() ) try
395 // check if this cell already the origin or not merged at all
396 Reference
< XMergeableCell
> xCell( xTable
->getCellByPosition( nMergedX
, nMergedY
), UNO_QUERY_THROW
);
397 if( !xCell
->isMerged() )
400 bool bCheckVert
= true;
401 bool bCheckHorz
= true;
403 sal_Int32 nMinCol
= 0;
404 sal_Int32 nMinRow
= 0;
406 sal_Int32 nStep
= 1, i
;
408 sal_Int32 nRow
, nCol
;
413 nRow
= nMergedY
- nStep
;
414 if( nRow
>= nMinRow
)
417 for( i
= 0; (i
<= nStep
) && (nCol
>= nMinCol
); i
++, nCol
-- )
419 if( checkMergeOrigin( xTable
, nMergedX
, nMergedY
, nCol
, nRow
, bCheckVert
) )
421 rOriginX
= nCol
; rOriginY
= nRow
;
427 if( nCol
== nMergedX
)
447 nCol
= nMergedX
- nStep
;
448 if( nCol
>= nMinCol
)
451 for( i
= 0; (i
< nStep
) && (nRow
>= nMinRow
); i
++, nRow
-- )
453 if( checkMergeOrigin( xTable
, nMergedX
, nMergedY
, nCol
, nRow
, bCheckHorz
) )
455 rOriginX
= nCol
; rOriginY
= nRow
;
461 if( nRow
== nMergedY
)
480 while( bCheckVert
|| bCheckHorz
);
484 OSL_FAIL("sdr::table::TableLayouter::findMergeOrigin(), exception caught!");
490 sal_Int32
TableLayouter::getMinimumColumnWidth( sal_Int32 nColumn
)
492 if( isValidColumn( nColumn
) )
494 return maColumns
[nColumn
].mnMinSize
;
498 OSL_FAIL( "TableLayouter::getMinimumColumnWidth(), column out of range!" );
504 sal_Int32
TableLayouter::distribute( LayoutVector
& rLayouts
, sal_Int32 nDistribute
)
506 // break loops after 100 runs to avoid freezing office due to developer error
507 sal_Int32 nSafe
= 100;
509 const std::size_t nCount
= rLayouts
.size();
512 bool bConstrainsBroken
= false;
516 bConstrainsBroken
= false;
518 // first enforce minimum size constrains on all entities
519 for( nIndex
= 0; nIndex
< nCount
; ++nIndex
)
521 Layout
& rLayout
= rLayouts
[nIndex
];
522 if( rLayout
.mnSize
< rLayout
.mnMinSize
)
525 bConstrainsBroken
|= o3tl::checked_sub(rLayout
.mnMinSize
, rLayout
.mnSize
, nDiff
);
526 bConstrainsBroken
|= o3tl::checked_sub(nDistribute
, nDiff
, nDistribute
);
527 rLayout
.mnSize
= rLayout
.mnMinSize
;
531 // calculate current width
532 // if nDistribute is < 0 (shrinking), entities that are already
533 // at minimum width are not counted
534 sal_Int32 nCurrentWidth
= 0;
535 for( nIndex
= 0; nIndex
< nCount
; ++nIndex
)
537 Layout
& rLayout
= rLayouts
[nIndex
];
538 if( (nDistribute
> 0) || (rLayout
.mnSize
> rLayout
.mnMinSize
) )
539 nCurrentWidth
= o3tl::saturating_add(nCurrentWidth
, rLayout
.mnSize
);
542 // now distribute over entities
543 if( (nCurrentWidth
!= 0) && (nDistribute
!= 0) )
545 sal_Int32 nDistributed
= nDistribute
;
546 for( nIndex
= 0; nIndex
< nCount
; ++nIndex
)
548 Layout
& rLayout
= rLayouts
[nIndex
];
549 if( (nDistribute
> 0) || (rLayout
.mnSize
> rLayout
.mnMinSize
) )
551 sal_Int32
n(nDistributed
); // for last entity use up rest
552 if (nIndex
!= (nCount
-1))
554 bConstrainsBroken
|= o3tl::checked_multiply(nDistribute
, rLayout
.mnSize
, n
);
558 bConstrainsBroken
|= o3tl::checked_add(rLayout
.mnSize
, n
, rLayout
.mnSize
);
561 if( rLayout
.mnSize
< rLayout
.mnMinSize
)
562 bConstrainsBroken
= true;
566 } while( bConstrainsBroken
&& --nSafe
);
569 for( nIndex
= 0; nIndex
< nCount
; ++nIndex
)
570 nSize
= o3tl::saturating_add(nSize
, rLayouts
[nIndex
].mnSize
);
576 typedef std::vector
< CellRef
> MergeableCellVector
;
577 typedef std::vector
< MergeableCellVector
> MergeVector
;
580 void TableLayouter::LayoutTableWidth( tools::Rectangle
& rArea
, bool bFit
)
582 const sal_Int32 nColCount
= getColumnCount();
583 const sal_Int32 nRowCount
= getRowCount();
587 MergeVector
aMergedCells( nColCount
);
588 std::vector
<sal_Int32
> aOptimalColumns
;
590 const OUString
sOptimalSize("OptimalSize");
592 if( sal::static_int_cast
< sal_Int32
>( maColumns
.size() ) != nColCount
)
593 maColumns
.resize( nColCount
);
595 Reference
< XTableColumns
> xCols( mxTable
->getColumns(), UNO_SET_THROW
);
597 // first calculate current width and initial minimum width per column,
598 // merged cells will be counted later
599 sal_Int32 nCurrentWidth
= 0;
600 sal_Int32 nCol
= 0, nRow
= 0;
601 for( nCol
= 0; nCol
< nColCount
; nCol
++ )
603 sal_Int32 nMinWidth
= 0;
605 bool bIsEmpty
= true; // check if all cells in this column are merged
607 for( nRow
= 0; nRow
< nRowCount
; ++nRow
)
609 CellRef
xCell( getCell( CellPos( nCol
, nRow
) ) );
610 if( xCell
.is() && !xCell
->isMerged() )
614 sal_Int32 nColSpan
= xCell
->getColumnSpan();
617 // merged cells will be evaluated later
618 aMergedCells
[nCol
+nColSpan
-1].push_back( xCell
);
622 nMinWidth
= std::max( nMinWidth
, xCell
->getMinimumWidth() );
627 maColumns
[nCol
].mnMinSize
= nMinWidth
;
631 maColumns
[nCol
].mnSize
= 0;
635 sal_Int32 nColWidth
= 0;
636 Reference
< XPropertySet
> xColSet( xCols
->getByIndex( nCol
), UNO_QUERY_THROW
);
637 bool bOptimal
= false;
638 xColSet
->getPropertyValue( sOptimalSize
) >>= bOptimal
;
641 aOptimalColumns
.push_back(nCol
);
645 xColSet
->getPropertyValue( gsSize
) >>= nColWidth
;
648 maColumns
[nCol
].mnSize
= std::max( nColWidth
, nMinWidth
);
650 nCurrentWidth
= o3tl::saturating_add(nCurrentWidth
, maColumns
[nCol
].mnSize
);
654 // if we have optimal sized rows, distribute what is given (left)
655 if( !bFit
&& !aOptimalColumns
.empty() && (nCurrentWidth
< rArea
.getWidth()) )
657 sal_Int32 nLeft
= rArea
.getWidth() - nCurrentWidth
;
658 sal_Int32 nDistribute
= nLeft
/ aOptimalColumns
.size();
660 auto iter( aOptimalColumns
.begin() );
661 while( iter
!= aOptimalColumns
.end() )
663 sal_Int32 nOptCol
= *iter
++;
664 if( iter
== aOptimalColumns
.end() )
667 maColumns
[nOptCol
].mnSize
+= nDistribute
;
668 nLeft
-= nDistribute
;
671 DBG_ASSERT( nLeft
== 0, "svx::TableLayouter::LayoutTableWidtht(), layouting failed!" );
674 // now check if merged cells fit
675 for( nCol
= 1; nCol
< nColCount
; ++nCol
)
677 bool bChanges
= false;
679 const sal_Int32 nOldSize
= maColumns
[nCol
].mnSize
;
681 for( const CellRef
& xCell
: aMergedCells
[nCol
] )
683 sal_Int32 nMinWidth
= xCell
->getMinimumWidth();
685 for( sal_Int32 nMCol
= nCol
- xCell
->getColumnSpan() + 1; (nMCol
> 0) && (nMCol
< nCol
); ++nMCol
)
686 nMinWidth
-= maColumns
[nMCol
].mnSize
;
688 if( nMinWidth
> maColumns
[nCol
].mnMinSize
)
689 maColumns
[nCol
].mnMinSize
= nMinWidth
;
691 if( nMinWidth
> maColumns
[nCol
].mnSize
)
693 maColumns
[nCol
].mnSize
= nMinWidth
;
700 nCurrentWidth
= o3tl::saturating_add(nCurrentWidth
, maColumns
[nCol
].mnSize
- nOldSize
);
704 // now scale if wanted and needed
705 if( bFit
&& (nCurrentWidth
!= rArea
.getWidth()) )
706 distribute( maColumns
, rArea
.getWidth() - nCurrentWidth
);
708 // last step, update left edges
709 sal_Int32 nNewWidth
= 0;
711 const bool bRTL
= (mxTable
->getSdrTableObj()->GetWritingMode() == WritingMode_RL_TB
);
712 RangeIterator
<sal_Int32
> coliter( 0, nColCount
, !bRTL
);
713 while( coliter
.next(nCol
) )
715 maColumns
[nCol
].mnPos
= nNewWidth
;
716 nNewWidth
= o3tl::saturating_add(nNewWidth
, maColumns
[nCol
].mnSize
);
719 Reference
< XPropertySet
> xColSet( xCols
->getByIndex(nCol
), UNO_QUERY_THROW
);
720 xColSet
->setPropertyValue( gsSize
, Any( maColumns
[nCol
].mnSize
) );
724 rArea
.SetSize( Size( nNewWidth
, rArea
.GetHeight() ) );
725 updateCells( rArea
);
729 void TableLayouter::LayoutTableHeight( tools::Rectangle
& rArea
, bool bFit
)
731 const sal_Int32 nColCount
= getColumnCount();
732 const sal_Int32 nRowCount
= getRowCount();
737 Reference
< XTableRows
> xRows( mxTable
->getRows() );
739 MergeVector
aMergedCells( nRowCount
);
740 std::vector
<sal_Int32
> aOptimalRows
;
742 const OUString
sOptimalSize("OptimalSize");
744 // first calculate current height and initial minimum size per column,
745 // merged cells will be counted later
746 sal_Int32 nCurrentHeight
= 0;
747 sal_Int32 nCol
, nRow
;
748 for( nRow
= 0; nRow
< nRowCount
; ++nRow
)
750 Reference
< XPropertySet
> xRowSet( xRows
->getByIndex(nRow
), UNO_QUERY_THROW
);
751 sal_Int32 nRowPropHeight
= 0;
752 xRowSet
->getPropertyValue( gsSize
) >>= nRowPropHeight
;
753 sal_Int32 nMinHeight
= 0;
755 bool bIsEmpty
= true; // check if all cells in this row are merged
756 bool bRowHasText
= false;
757 bool bRowHasCellInEditMode
= false;
759 for( nCol
= 0; nCol
< nColCount
; ++nCol
)
761 CellRef
xCell( getCell( CellPos( nCol
, nRow
) ) );
762 if( xCell
.is() && !xCell
->isMerged() )
766 sal_Int32 nRowSpan
= xCell
->getRowSpan();
769 // merged cells will be evaluated later
770 aMergedCells
[nRow
+nRowSpan
-1].push_back( xCell
);
774 bool bCellHasText
= xCell
->hasText();
775 bool bCellInEditMode
= xCell
->IsTextEditActive();
777 if (!bRowHasCellInEditMode
&& bCellInEditMode
)
778 bRowHasCellInEditMode
= true;
780 if ((bRowHasText
== bCellHasText
) || (bRowHasText
&& bCellInEditMode
))
782 nMinHeight
= std::max( nMinHeight
, xCell
->getMinimumHeight() );
784 else if ( !bRowHasText
&& bCellHasText
)
787 nMinHeight
= xCell
->getMinimumHeight();
790 // tdf#137949 We should consider "Heigth" property while calculating minimum height.
791 // This control decides when we use "Heigth" property value instead of calculated minimum height
792 // Case 1: * Row has "Heigth" property
793 // * Calculated minimum heigth is smaller than Height propery value.
794 // Case 2: * Row has "Heigth" property
795 // * Calculated minimum heigth is bigger than Height propery value and
796 // * Row has not any text of any cell in edit mode in the row (means completely empty)
797 if ((nMinHeight
< nRowPropHeight
&& nRowPropHeight
> 0 ) ||
798 (nMinHeight
> nRowPropHeight
&& nRowPropHeight
> 0 && (!bRowHasText
&& !bRowHasCellInEditMode
)))
800 nMinHeight
= nRowPropHeight
;
806 maRows
[nRow
].mnMinSize
= nMinHeight
;
810 maRows
[nRow
].mnSize
= 0;
814 sal_Int32 nRowHeight
= 0;
816 bool bOptimal
= false;
817 xRowSet
->getPropertyValue( sOptimalSize
) >>= bOptimal
;
820 aOptimalRows
.push_back( nRow
);
824 xRowSet
->getPropertyValue( gsSize
) >>= nRowHeight
;
827 maRows
[nRow
].mnSize
= nRowHeight
;
829 if( maRows
[nRow
].mnSize
< nMinHeight
)
830 maRows
[nRow
].mnSize
= nMinHeight
;
832 nCurrentHeight
= o3tl::saturating_add(nCurrentHeight
, maRows
[nRow
].mnSize
);
836 // if we have optimal sized rows, distribute what is given (left)
837 if( !bFit
&& !aOptimalRows
.empty() && (nCurrentHeight
< rArea
.getHeight()) )
839 sal_Int32 nLeft
= rArea
.getHeight() - nCurrentHeight
;
840 sal_Int32 nDistribute
= nLeft
/ aOptimalRows
.size();
842 auto iter( aOptimalRows
.begin() );
843 while( iter
!= aOptimalRows
.end() )
845 sal_Int32 nOptRow
= *iter
++;
846 if( iter
== aOptimalRows
.end() )
849 maRows
[nOptRow
].mnSize
+= nDistribute
;
850 nLeft
-= nDistribute
;
854 DBG_ASSERT( nLeft
== 0, "svx::TableLayouter::LayoutTableHeight(), layouting failed!" );
857 // now check if merged cells fit
858 for( nRow
= 1; nRow
< nRowCount
; ++nRow
)
860 bool bChanges
= false;
861 sal_Int32 nOldSize
= maRows
[nRow
].mnSize
;
863 for( const CellRef
& xCell
: aMergedCells
[nRow
] )
865 sal_Int32 nMinHeight
= xCell
->getMinimumHeight();
867 for( sal_Int32 nMRow
= nRow
- xCell
->getRowSpan() + 1; (nMRow
> 0) && (nMRow
< nRow
); ++nMRow
)
868 nMinHeight
-= maRows
[nMRow
].mnSize
;
870 if( nMinHeight
> maRows
[nRow
].mnMinSize
)
871 maRows
[nRow
].mnMinSize
= nMinHeight
;
873 if( nMinHeight
> maRows
[nRow
].mnSize
)
875 maRows
[nRow
].mnSize
= nMinHeight
;
880 nCurrentHeight
= o3tl::saturating_add(nCurrentHeight
, maRows
[nRow
].mnSize
- nOldSize
);
883 // now scale if wanted and needed
884 if( bFit
&& nCurrentHeight
!= rArea
.getHeight() )
885 distribute(maRows
, o3tl::saturating_sub
<sal_Int32
>(rArea
.getHeight(), nCurrentHeight
));
887 // last step, update left edges
888 sal_Int32 nNewHeight
= 0;
889 for( nRow
= 0; nRow
< nRowCount
; ++nRow
)
891 maRows
[nRow
].mnPos
= nNewHeight
;
892 nNewHeight
= o3tl::saturating_add(nNewHeight
, maRows
[nRow
].mnSize
);
896 Reference
< XPropertySet
> xRowSet( xRows
->getByIndex(nRow
), UNO_QUERY_THROW
);
897 xRowSet
->setPropertyValue( gsSize
, Any( maRows
[nRow
].mnSize
) );
901 rArea
.SetSize( Size( rArea
.GetWidth(), nNewHeight
) );
902 updateCells( rArea
);
906 /** try to fit the table into the given rectangle.
907 If the rectangle is too small, it will be grown to fit the table. */
908 void TableLayouter::LayoutTable( tools::Rectangle
& rRectangle
, bool bFitWidth
, bool bFitHeight
)
913 const sal_Int32 nRowCount
= mxTable
->getRowCount();
914 const sal_Int32 nColCount
= mxTable
->getColumnCount();
916 if( (nRowCount
!= getRowCount()) || (nColCount
!= getColumnCount()) )
918 if( static_cast< sal_Int32
>( maRows
.size() ) != nRowCount
)
919 maRows
.resize( nRowCount
);
921 for( sal_Int32 nRow
= 0; nRow
< nRowCount
; nRow
++ )
922 maRows
[nRow
].clear();
924 if( static_cast< sal_Int32
>( maColumns
.size() ) != nColCount
)
925 maColumns
.resize( nColCount
);
927 for( sal_Int32 nCol
= 0; nCol
< nColCount
; nCol
++ )
928 maColumns
[nCol
].clear();
931 LayoutTableWidth( rRectangle
, bFitWidth
);
932 LayoutTableHeight( rRectangle
, bFitHeight
);
933 UpdateBorderLayout();
937 void TableLayouter::updateCells( tools::Rectangle
const & rRectangle
)
939 const sal_Int32 nColCount
= getColumnCount();
940 const sal_Int32 nRowCount
= getRowCount();
943 for( aPos
.mnRow
= 0; aPos
.mnRow
< nRowCount
; aPos
.mnRow
++ )
945 for( aPos
.mnCol
= 0; aPos
.mnCol
< nColCount
; aPos
.mnCol
++ )
947 CellRef
xCell( getCell( aPos
) );
950 basegfx::B2IRectangle aCellArea
;
951 if( getCellArea( xCell
, aPos
, aCellArea
) )
953 tools::Rectangle aCellRect
;
954 aCellRect
.SetLeft( aCellArea
.getMinX() );
955 aCellRect
.SetRight( aCellArea
.getMaxX() );
956 aCellRect
.SetTop( aCellArea
.getMinY() );
957 aCellRect
.SetBottom( aCellArea
.getMaxY() );
958 aCellRect
.Move( rRectangle
.Left(), rRectangle
.Top() );
959 xCell
->setCellRect( aCellRect
);
967 CellRef
TableLayouter::getCell( const CellPos
& rPos
) const
970 if( mxTable
.is() ) try
972 xCell
.set( dynamic_cast< Cell
* >( mxTable
->getCellByPosition( rPos
.mnCol
, rPos
.mnRow
).get() ) );
976 OSL_FAIL( "sdr::table::TableLayouter::getCell(), exception caught!" );
982 bool TableLayouter::HasPriority( const SvxBorderLine
* pThis
, const SvxBorderLine
* pOther
)
984 if (!pThis
|| ((pThis
== &gEmptyBorder
) && (pOther
!= nullptr)))
986 if (!pOther
|| (pOther
== &gEmptyBorder
))
989 sal_uInt16 nThisSize
= pThis
->GetScaledWidth();
990 sal_uInt16 nOtherSize
= pOther
->GetScaledWidth();
992 if (nThisSize
> nOtherSize
)
995 else if (nThisSize
< nOtherSize
)
1001 if ( pOther
->GetInWidth() && !pThis
->GetInWidth() )
1005 else if ( pThis
->GetInWidth() && !pOther
->GetInWidth() )
1011 return true; //! ???
1016 void TableLayouter::SetBorder( sal_Int32 nCol
, sal_Int32 nRow
, bool bHorizontal
, const SvxBorderLine
* pLine
)
1019 pLine
= &gEmptyBorder
;
1021 BorderLineMap
& rMap
= bHorizontal
? maHorizontalBorders
: maVerticalBorders
;
1023 if( (nCol
>= 0) && (nCol
< sal::static_int_cast
<sal_Int32
>(rMap
.size())) &&
1024 (nRow
>= 0) && (nRow
< sal::static_int_cast
<sal_Int32
>(rMap
[nCol
].size())) )
1026 SvxBorderLine
*pOld
= rMap
[nCol
][nRow
];
1028 if (HasPriority(pLine
, pOld
))
1030 if (pOld
&& pOld
!= &gEmptyBorder
)
1033 SvxBorderLine
* pNew
= (pLine
!= &gEmptyBorder
) ? new SvxBorderLine(*pLine
) : &gEmptyBorder
;
1035 rMap
[nCol
][nRow
] = pNew
;
1040 OSL_FAIL( "sdr::table::TableLayouter::SetBorder(), invalid border!" );
1044 void TableLayouter::ClearBorderLayout()
1046 ClearBorderLayout(maHorizontalBorders
);
1047 ClearBorderLayout(maVerticalBorders
);
1050 void TableLayouter::ClearBorderLayout(BorderLineMap
& rMap
)
1052 const sal_Int32 nColCount
= rMap
.size();
1054 for( sal_Int32 nCol
= 0; nCol
< nColCount
; nCol
++ )
1056 const sal_Int32 nRowCount
= rMap
[nCol
].size();
1057 for( sal_Int32 nRow
= 0; nRow
< nRowCount
; nRow
++ )
1059 SvxBorderLine
* pLine
= rMap
[nCol
][nRow
];
1062 if( pLine
!= &gEmptyBorder
)
1065 rMap
[nCol
][nRow
] = nullptr;
1071 void TableLayouter::ResizeBorderLayout()
1073 ClearBorderLayout();
1074 ResizeBorderLayout(maHorizontalBorders
);
1075 ResizeBorderLayout(maVerticalBorders
);
1079 void TableLayouter::ResizeBorderLayout( BorderLineMap
& rMap
)
1081 const sal_Int32 nColCount
= getColumnCount() + 1;
1082 const sal_Int32 nRowCount
= getRowCount() + 1;
1084 if( sal::static_int_cast
<sal_Int32
>(rMap
.size()) != nColCount
)
1085 rMap
.resize( nColCount
);
1087 for( sal_Int32 nCol
= 0; nCol
< nColCount
; nCol
++ )
1089 if( sal::static_int_cast
<sal_Int32
>(rMap
[nCol
].size()) != nRowCount
)
1090 rMap
[nCol
].resize( nRowCount
);
1095 void TableLayouter::UpdateBorderLayout()
1097 // make sure old border layout is cleared and border maps have correct size
1098 ResizeBorderLayout();
1100 const sal_Int32 nColCount
= getColumnCount();
1101 const sal_Int32 nRowCount
= getRowCount();
1104 for( aPos
.mnRow
= 0; aPos
.mnRow
< nRowCount
; aPos
.mnRow
++ )
1106 for( aPos
.mnCol
= 0; aPos
.mnCol
< nColCount
; aPos
.mnCol
++ )
1108 CellRef
xCell( getCell( aPos
) );
1112 const SvxBoxItem
* pThisAttr
= xCell
->GetItemSet().GetItem
<SvxBoxItem
>( SDRATTR_TABLE_BORDER
);
1113 OSL_ENSURE(pThisAttr
,"sdr::table::TableLayouter::UpdateBorderLayout(), no border attribute?");
1118 const sal_Int32 nLastRow
= xCell
->getRowSpan() + aPos
.mnRow
;
1119 const sal_Int32 nLastCol
= xCell
->getColumnSpan() + aPos
.mnCol
;
1121 for( sal_Int32 nRow
= aPos
.mnRow
; nRow
< nLastRow
; nRow
++ )
1123 SetBorder( aPos
.mnCol
, nRow
, false, pThisAttr
->GetLeft() );
1124 SetBorder( nLastCol
, nRow
, false, pThisAttr
->GetRight() );
1127 for( sal_Int32 nCol
= aPos
.mnCol
; nCol
< nLastCol
; nCol
++ )
1129 SetBorder( nCol
, aPos
.mnRow
, true, pThisAttr
->GetTop() );
1130 SetBorder( nCol
, nLastRow
, true, pThisAttr
->GetBottom() );
1137 void TableLayouter::DistributeColumns( ::tools::Rectangle
& rArea
,
1138 sal_Int32 nFirstCol
,
1140 const bool bOptimize
,
1141 const bool bMinimize
)
1143 if( mxTable
.is() ) try
1145 const sal_Int32 nColCount
= getColumnCount();
1146 Reference
< XTableColumns
> xCols( mxTable
->getColumns(), UNO_SET_THROW
);
1147 const Size
aSize(0xffffff, 0xffffff);
1149 //special case - optimize a single column
1150 if ( (bOptimize
|| bMinimize
) && nFirstCol
== nLastCol
)
1152 const sal_Int32 nWish
= calcPreferredColumnWidth(nFirstCol
, aSize
);
1153 if ( nWish
< getColumnWidth(nFirstCol
) )
1155 Reference
< XPropertySet
> xColSet( xCols
->getByIndex(nFirstCol
), UNO_QUERY_THROW
);
1156 xColSet
->setPropertyValue( gsSize
, Any( nWish
) );
1158 //FitWidth automatically distributes the new excess space
1159 LayoutTable( rArea
, /*bFitWidth=*/!bMinimize
, /*bFitHeight=*/false );
1163 if( (nFirstCol
< 0) || (nFirstCol
>= nLastCol
) || (nLastCol
>= nColCount
) )
1166 sal_Int32 nAllWidth
= 0;
1168 sal_Int32 nUnused
= 0;
1169 std::vector
<sal_Int32
> aWish(nColCount
);
1171 for( sal_Int32 nCol
= nFirstCol
; nCol
<= nLastCol
; ++nCol
)
1172 nAllWidth
+= getColumnWidth(nCol
);
1174 const sal_Int32 nEqualWidth
= nAllWidth
/ (nLastCol
-nFirstCol
+1);
1176 //pass 1 - collect unneeded space (from an equal width perspective)
1177 if ( bMinimize
|| bOptimize
)
1179 for( sal_Int32 nCol
= nFirstCol
; nCol
<= nLastCol
; ++nCol
)
1181 const sal_Int32 nIndex
= nCol
- nFirstCol
;
1182 aWish
[nIndex
] = calcPreferredColumnWidth(nCol
, aSize
);
1183 fAllWish
+= aWish
[nIndex
];
1184 if ( aWish
[nIndex
] < nEqualWidth
)
1185 nUnused
+= nEqualWidth
- aWish
[nIndex
];
1188 const sal_Int32 nDistributeExcess
= nAllWidth
- fAllWish
;
1190 sal_Int32 nWidth
= nEqualWidth
;
1191 for( sal_Int32 nCol
= nFirstCol
; nCol
<= nLastCol
; ++nCol
)
1193 if ( !bMinimize
&& nCol
== nLastCol
)
1194 nWidth
= nAllWidth
; // last column gets rounding/logic errors
1195 else if ( (bMinimize
|| bOptimize
) && fAllWish
)
1197 //pass 2 - first come, first served when requesting from the
1198 // unneeded pool, or proportionally allocate excess.
1199 const sal_Int32 nIndex
= nCol
- nFirstCol
;
1200 if ( aWish
[nIndex
] > nEqualWidth
+ nUnused
)
1202 nWidth
= nEqualWidth
+ nUnused
;
1207 nWidth
= aWish
[nIndex
];
1208 if ( aWish
[nIndex
] > nEqualWidth
)
1209 nUnused
-= aWish
[nIndex
] - nEqualWidth
;
1211 if ( !bMinimize
&& nDistributeExcess
> 0 )
1212 nWidth
+= nWidth
/ fAllWish
* nDistributeExcess
;
1216 Reference
< XPropertySet
> xColSet( xCols
->getByIndex( nCol
), UNO_QUERY_THROW
);
1217 xColSet
->setPropertyValue( gsSize
, Any( nWidth
) );
1219 nAllWidth
-= nWidth
;
1222 LayoutTable( rArea
, !bMinimize
, false );
1226 OSL_FAIL("sdr::table::TableLayouter::DistributeColumns(), exception caught!");
1231 void TableLayouter::DistributeRows( ::tools::Rectangle
& rArea
,
1232 sal_Int32 nFirstRow
,
1234 const bool bOptimize
,
1235 const bool bMinimize
)
1237 if( mxTable
.is() ) try
1239 const sal_Int32 nRowCount
= mxTable
->getRowCount();
1240 Reference
< XTableRows
> xRows( mxTable
->getRows(), UNO_SET_THROW
);
1241 sal_Int32 nMinHeight
= 0;
1243 //special case - minimize a single row
1244 if ( bMinimize
&& nFirstRow
== nLastRow
)
1246 const sal_Int32 nWish
= std::max( maRows
[nFirstRow
].mnMinSize
, nMinHeight
);
1247 if ( nWish
< getRowHeight(nFirstRow
) )
1249 Reference
< XPropertySet
> xRowSet( xRows
->getByIndex( nFirstRow
), UNO_QUERY_THROW
);
1250 xRowSet
->setPropertyValue( gsSize
, Any( nWish
) );
1252 LayoutTable( rArea
, /*bFitWidth=*/false, /*bFitHeight=*/!bMinimize
);
1256 if( (nFirstRow
< 0) || (nFirstRow
>= nLastRow
) || (nLastRow
>= nRowCount
) )
1259 sal_Int32 nAllHeight
= 0;
1260 sal_Int32 nMaxHeight
= 0;
1262 for( sal_Int32 nRow
= nFirstRow
; nRow
<= nLastRow
; ++nRow
)
1264 nMinHeight
= std::max( maRows
[nRow
].mnMinSize
, nMinHeight
);
1265 nMaxHeight
= std::max( maRows
[nRow
].mnSize
, nMaxHeight
);
1266 nAllHeight
+= maRows
[nRow
].mnSize
;
1269 const sal_Int32 nRows
= nLastRow
-nFirstRow
+1;
1270 sal_Int32 nHeight
= nAllHeight
/ nRows
;
1272 if ( !bMinimize
&& nHeight
< nMaxHeight
)
1276 sal_Int32 nNeededHeight
= nRows
* nMaxHeight
;
1277 rArea
.AdjustBottom(nNeededHeight
- nAllHeight
);
1278 nHeight
= nMaxHeight
;
1279 nAllHeight
= nRows
* nMaxHeight
;
1281 else if ( nHeight
< nMinHeight
)
1283 sal_Int32 nNeededHeight
= nRows
* nMinHeight
;
1284 rArea
.AdjustBottom(nNeededHeight
- nAllHeight
);
1285 nHeight
= nMinHeight
;
1286 nAllHeight
= nRows
* nMinHeight
;
1290 for( sal_Int32 nRow
= nFirstRow
; nRow
<= nLastRow
; ++nRow
)
1293 nHeight
= maRows
[nRow
].mnMinSize
;
1294 else if ( nRow
== nLastRow
)
1295 nHeight
= nAllHeight
; // last row get round errors
1297 Reference
< XPropertySet
> xRowSet( xRows
->getByIndex( nRow
), UNO_QUERY_THROW
);
1298 xRowSet
->setPropertyValue( gsSize
, Any( nHeight
) );
1300 nAllHeight
-= nHeight
;
1303 LayoutTable( rArea
, false, !bMinimize
);
1307 OSL_FAIL("sdr::table::TableLayouter::DistributeRows(), exception caught!");
1311 void TableLayouter::dumpAsXml(xmlTextWriterPtr pWriter
) const
1313 xmlTextWriterStartElement(pWriter
, BAD_CAST("TableLayouter"));
1315 xmlTextWriterStartElement(pWriter
, BAD_CAST("columns"));
1316 for (const auto& rColumn
: maColumns
)
1317 rColumn
.dumpAsXml(pWriter
);
1318 xmlTextWriterEndElement(pWriter
);
1320 xmlTextWriterStartElement(pWriter
, BAD_CAST("rows"));
1321 for (const auto& rRow
: maRows
)
1322 rRow
.dumpAsXml(pWriter
);
1323 xmlTextWriterEndElement(pWriter
);
1325 xmlTextWriterEndElement(pWriter
);
1328 void TableLayouter::Layout::dumpAsXml(xmlTextWriterPtr pWriter
) const
1330 xmlTextWriterStartElement(pWriter
, BAD_CAST("TableLayouter_Layout"));
1332 xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("pos"), BAD_CAST(OString::number(mnPos
).getStr()));
1333 xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("size"), BAD_CAST(OString::number(mnSize
).getStr()));
1334 xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("minSize"), BAD_CAST(OString::number(mnMinSize
).getStr()));
1336 xmlTextWriterEndElement(pWriter
);
1341 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */