tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / svx / source / table / tablelayouter.cxx
blobe165f5219480873e36266f9a153b1835fd4d619a
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 <comphelper/diagnose_ex.hxx>
25 #include <tools/gen.hxx>
26 #include <libxml/xmlwriter.h>
28 #include <cell.hxx>
29 #include <o3tl/safeint.hxx>
30 #include <tablemodel.hxx>
31 #include "tablelayouter.hxx"
32 #include <svx/svdotable.hxx>
33 #include <editeng/borderline.hxx>
34 #include <editeng/boxitem.hxx>
35 #include <utility>
37 using ::editeng::SvxBorderLine;
38 using namespace ::com::sun::star::uno;
39 using namespace ::com::sun::star::container;
40 using namespace ::com::sun::star::beans;
41 using namespace ::com::sun::star::table;
42 using namespace ::com::sun::star::text;
45 namespace sdr::table {
48 static SvxBorderLine gEmptyBorder;
50 constexpr OUString gsSize( u"Size"_ustr );
52 TableLayouter::TableLayouter( TableModelRef xTableModel )
53 : mxTable(std::move( xTableModel ))
58 TableLayouter::~TableLayouter()
60 ClearBorderLayout();
64 basegfx::B2ITuple TableLayouter::getCellSize( const CellRef& xCell, const CellPos& rPos ) const
66 sal_Int32 width = 0;
67 sal_Int32 height = 0;
69 try
71 if( xCell.is() && !xCell->isMerged() )
73 CellPos aPos( rPos );
75 sal_Int32 nRowCount = getRowCount();
76 sal_Int32 nRowSpan = std::max( xCell->getRowSpan(), sal_Int32(1) );
77 while( nRowSpan && (aPos.mnRow < nRowCount) )
79 if( static_cast<sal_Int32>(maRows.size()) <= aPos.mnRow )
80 break;
82 height = o3tl::saturating_add(height, maRows[aPos.mnRow++].mnSize);
83 nRowSpan--;
86 sal_Int32 nColCount = getColumnCount();
87 sal_Int32 nColSpan = std::max( xCell->getColumnSpan(), sal_Int32(1) );
88 while( nColSpan && (aPos.mnCol < nColCount ) )
90 if( static_cast<sal_Int32>(maColumns.size()) <= aPos.mnCol )
91 break;
93 width = o3tl::saturating_add(width, maColumns[aPos.mnCol++].mnSize);
94 nColSpan--;
98 catch( Exception& )
100 TOOLS_WARN_EXCEPTION("svx", "");
103 return basegfx::B2ITuple( width, height );
107 bool TableLayouter::getCellArea( const CellRef& xCell, const CellPos& rPos, basegfx::B2IRectangle& rArea ) const
111 if( xCell.is() && !xCell->isMerged() && isValid(rPos) )
113 const basegfx::B2ITuple aCellSize( getCellSize( xCell, rPos ) );
114 const bool bRTL = (mxTable->getSdrTableObj()->GetWritingMode() == WritingMode_RL_TB);
116 if( (rPos.mnCol < static_cast<sal_Int32>(maColumns.size())) && (rPos.mnRow < static_cast<sal_Int32>(maRows.size()) ) )
118 const sal_Int32 y = maRows[rPos.mnRow].mnPos;
120 sal_Int32 endy;
121 if (o3tl::checked_add(y, aCellSize.getY(), endy))
122 return false;
124 if(bRTL)
126 ///For RTL Table Calculate the Right End of cell instead of Left
127 const sal_Int32 x = maColumns[rPos.mnCol].mnPos + maColumns[rPos.mnCol].mnSize;
128 sal_Int32 startx;
129 if (o3tl::checked_sub(x, aCellSize.getX(), startx))
130 return false;
131 rArea = basegfx::B2IRectangle(startx, y, x, endy);
133 else
135 const sal_Int32 x = maColumns[rPos.mnCol].mnPos;
136 sal_Int32 endx;
137 if (o3tl::checked_add(x, aCellSize.getX(), endx))
138 return false;
139 rArea = basegfx::B2IRectangle(x, y, endx, endy);
141 return true;
145 catch( Exception& )
147 TOOLS_WARN_EXCEPTION("svx", "");
149 return false;
153 sal_Int32 TableLayouter::getRowHeight( sal_Int32 nRow ) const
155 if( isValidRow(nRow) )
156 return maRows[nRow].mnSize;
157 else
158 return 0;
162 sal_Int32 TableLayouter::getColumnWidth( sal_Int32 nColumn ) const
164 if( isValidColumn(nColumn) )
165 return maColumns[nColumn].mnSize;
166 else
167 return 0;
170 sal_Int32 TableLayouter::calcPreferredColumnWidth( sal_Int32 nColumn, Size aSize ) const
172 sal_Int32 nRet = 0;
173 for ( sal_uInt32 nRow = 0; nRow < static_cast<sal_uInt32>(maRows.size()); ++nRow )
175 // Account for the space desired by spanned columns.
176 // Only the last spanned cell will try to ensure sufficient space,
177 // by looping through the previous columns, subtracting their portion.
178 sal_Int32 nWish = 0;
179 sal_Int32 nSpannedColumn = nColumn;
180 bool bFindSpan = true;
181 while ( bFindSpan && isValidColumn(nSpannedColumn) )
183 // recursive function call gets earlier portion of spanned column.
184 if ( nSpannedColumn < nColumn )
185 nWish -= calcPreferredColumnWidth( nSpannedColumn, aSize );
187 CellRef xCell( getCell( CellPos( nSpannedColumn, nRow ) ) );
188 if ( xCell.is() && !xCell->isMerged()
189 && (xCell->getColumnSpan() == 1 || nSpannedColumn < nColumn) )
191 nWish += xCell->calcPreferredWidth(aSize);
192 bFindSpan = false;
194 else if ( xCell.is() && xCell->isMerged()
195 && nColumn == nSpannedColumn
196 && isValidColumn(nColumn + 1) )
198 xCell = getCell( CellPos( nColumn + 1, nRow ) );
199 bFindSpan = xCell.is() && !xCell->isMerged();
201 nSpannedColumn--;
203 nRet = std::max( nRet, nWish );
205 return nRet;
209 bool TableLayouter::isEdgeVisible( sal_Int32 nEdgeX, sal_Int32 nEdgeY, bool bHorizontal ) const
211 const BorderLineMap& rMap = bHorizontal ? maHorizontalBorders : maVerticalBorders;
213 if( (nEdgeX >= 0) && (nEdgeX < sal::static_int_cast<sal_Int32>(rMap.size())) &&
214 (nEdgeY >= 0) && (nEdgeY < sal::static_int_cast<sal_Int32>(rMap[nEdgeX].size())) )
216 return rMap[nEdgeX][nEdgeY] != nullptr;
218 else
220 OSL_FAIL( "sdr::table::TableLayouter::getBorderLine(), invalid edge!" );
223 return false;
227 /** returns the requested borderline in rpBorderLine or a null pointer if there is no border at this edge */
228 SvxBorderLine* TableLayouter::getBorderLine( sal_Int32 nEdgeX, sal_Int32 nEdgeY, bool bHorizontal )const
230 SvxBorderLine* pLine = nullptr;
232 const BorderLineMap& rMap = bHorizontal ? maHorizontalBorders : maVerticalBorders;
234 if( (nEdgeX >= 0) && (nEdgeX < sal::static_int_cast<sal_Int32>(rMap.size())) &&
235 (nEdgeY >= 0) && (nEdgeY < sal::static_int_cast<sal_Int32>(rMap[nEdgeX].size())) )
237 pLine = rMap[nEdgeX][nEdgeY];
238 if( pLine == &gEmptyBorder )
239 pLine = nullptr;
241 else
243 OSL_FAIL( "sdr::table::TableLayouter::getBorderLine(), invalid edge!" );
246 return pLine;
249 std::vector<EdgeInfo> TableLayouter::getHorizontalEdges()
251 std::vector<EdgeInfo> aReturn;
252 sal_Int32 nRowSize = sal_Int32(maRows.size());
253 for (sal_Int32 i = 0; i <= nRowSize; i++)
255 sal_Int32 nEdgeMin = 0;
256 sal_Int32 nEdgeMax = 0;
257 sal_Int32 nEdge = getHorizontalEdge(i, &nEdgeMin, &nEdgeMax);
258 nEdgeMin -= nEdge;
259 nEdgeMax -= nEdge;
260 aReturn.emplace_back(i, nEdge, nEdgeMin, nEdgeMax);
262 return aReturn;
265 std::vector<EdgeInfo> TableLayouter::getVerticalEdges()
267 std::vector<EdgeInfo> aReturn;
268 sal_Int32 nColumnSize = sal_Int32(maColumns.size());
269 for (sal_Int32 i = 0; i <= nColumnSize; i++)
271 sal_Int32 nEdgeMin = 0;
272 sal_Int32 nEdgeMax = 0;
273 sal_Int32 nEdge = getVerticalEdge(i, &nEdgeMin, &nEdgeMax);
274 nEdgeMin -= nEdge;
275 nEdgeMax -= nEdge;
276 aReturn.emplace_back(i, nEdge, nEdgeMin, nEdgeMax);
278 return aReturn;
281 sal_Int32 TableLayouter::getHorizontalEdge( int nEdgeY, sal_Int32* pnMin /*= 0*/, sal_Int32* pnMax /*= 0*/ )
283 sal_Int32 nRet = 0;
284 const sal_Int32 nRowCount = getRowCount();
285 if( (nEdgeY >= 0) && (nEdgeY <= nRowCount ) )
286 nRet = maRows[std::min(static_cast<sal_Int32>(nEdgeY),nRowCount-1)].mnPos;
288 if( nEdgeY == nRowCount )
289 nRet += maRows[nEdgeY - 1].mnSize;
291 if( pnMin )
293 if( (nEdgeY > 0) && (nEdgeY <= nRowCount ) )
295 *pnMin = maRows[nEdgeY-1].mnPos + 600; // todo
297 else
299 *pnMin = nRet;
303 if( pnMax )
305 *pnMax = 0x0fffffff;
307 return nRet;
311 sal_Int32 TableLayouter::getVerticalEdge( int nEdgeX, sal_Int32* pnMin /*= 0*/, sal_Int32* pnMax /*= 0*/ )
313 sal_Int32 nRet = 0;
315 const sal_Int32 nColCount = getColumnCount();
316 if( (nEdgeX >= 0) && (nEdgeX <= nColCount ) )
317 nRet = maColumns[std::min(static_cast<sal_Int32>(nEdgeX),nColCount-1)].mnPos;
319 const bool bRTL = (mxTable->getSdrTableObj()->GetWritingMode() == WritingMode_RL_TB);
320 if( bRTL )
322 if( (nEdgeX >= 0) && (nEdgeX < nColCount) )
323 nRet += maColumns[nEdgeX].mnSize;
325 else
327 if( nEdgeX == nColCount )
328 nRet += maColumns[nEdgeX - 1].mnSize;
331 if( pnMin )
333 *pnMin = nRet;
334 if( bRTL )
336 if( nEdgeX < nColCount )
337 *pnMin = nRet - maColumns[nEdgeX].mnSize + getMinimumColumnWidth(nEdgeX);
339 else
341 if( (nEdgeX > 0) && (nEdgeX <= nColCount ) )
342 *pnMin = maColumns[nEdgeX-1].mnPos + getMinimumColumnWidth( nEdgeX-1 );
346 if( pnMax )
348 *pnMax = 0x0fffffff; // todo
349 if( bRTL )
351 if( nEdgeX > 0 )
352 *pnMax = nRet + maColumns[nEdgeX-1].mnSize - getMinimumColumnWidth( nEdgeX-1 );
354 else
356 if( (nEdgeX >= 0) && (nEdgeX < nColCount ) )
357 *pnMax = maColumns[nEdgeX].mnPos + maColumns[nEdgeX].mnSize - getMinimumColumnWidth( nEdgeX );
361 return nRet;
365 static bool checkMergeOrigin( const TableModelRef& xTable, sal_Int32 nMergedX, sal_Int32 nMergedY, sal_Int32 nCellX, sal_Int32 nCellY, bool& bRunning )
367 Reference< XMergeableCell > xCell( xTable->getCellByPosition( nCellX, nCellY ), UNO_QUERY );
368 if( xCell.is() && !xCell->isMerged() )
370 const sal_Int32 nRight = xCell->getColumnSpan() + nCellX;
371 const sal_Int32 nBottom = xCell->getRowSpan() + nCellY;
372 if( (nMergedX < nRight) && (nMergedY < nBottom) )
373 return true;
375 bRunning = false;
377 return false;
380 /** returns true if the cell(nMergedX,nMergedY) is merged with other cells.
381 the returned cell( rOriginX, rOriginY ) is the origin( top left cell ) of the merge.
383 bool findMergeOrigin( const TableModelRef& xTable, sal_Int32 nMergedX, sal_Int32 nMergedY, sal_Int32& rOriginX, sal_Int32& rOriginY )
385 rOriginX = nMergedX;
386 rOriginY = nMergedY;
388 if( xTable.is() ) try
390 // check if this cell already the origin or not merged at all
391 Reference< XMergeableCell > xCell( xTable->getCellByPosition( nMergedX, nMergedY ), UNO_QUERY_THROW );
392 if( !xCell->isMerged() )
393 return true;
395 bool bCheckVert = true;
396 bool bCheckHorz = true;
398 sal_Int32 nMinCol = 0;
399 sal_Int32 nMinRow = 0;
401 sal_Int32 nStep = 1, i;
403 sal_Int32 nRow, nCol;
406 if( bCheckVert )
408 nRow = nMergedY - nStep;
409 if( nRow >= nMinRow )
411 nCol = nMergedX;
412 for( i = 0; (i <= nStep) && (nCol >= nMinCol); i++, nCol-- )
414 if( checkMergeOrigin( xTable, nMergedX, nMergedY, nCol, nRow, bCheckVert ) )
416 rOriginX = nCol; rOriginY = nRow;
417 return true;
420 if( !bCheckVert )
422 if( nCol == nMergedX )
424 nMinRow = nRow+1;
426 else
428 bCheckVert = true;
430 break;
434 else
436 bCheckVert = false;
440 if( bCheckHorz )
442 nCol = nMergedX - nStep;
443 if( nCol >= nMinCol )
445 nRow = nMergedY;
446 for( i = 0; (i < nStep) && (nRow >= nMinRow); i++, nRow-- )
448 if( checkMergeOrigin( xTable, nMergedX, nMergedY, nCol, nRow, bCheckHorz ) )
450 rOriginX = nCol; rOriginY = nRow;
451 return true;
454 if( !bCheckHorz )
456 if( nRow == nMergedY )
458 nMinCol = nCol+1;
460 else
462 bCheckHorz = true;
464 break;
468 else
470 bCheckHorz = false;
473 nStep++;
475 while( bCheckVert || bCheckHorz );
477 catch( Exception& )
479 TOOLS_WARN_EXCEPTION("svx", "");
481 return false;
485 sal_Int32 TableLayouter::getMinimumColumnWidth( sal_Int32 nColumn )
487 if( isValidColumn( nColumn ) )
489 return maColumns[nColumn].mnMinSize;
491 else
493 OSL_FAIL( "TableLayouter::getMinimumColumnWidth(), column out of range!" );
494 return 0;
499 sal_Int32 TableLayouter::distribute( LayoutVector& rLayouts, sal_Int32 nDistribute )
501 // break loops after 100 runs to avoid freezing office due to developer error
502 sal_Int32 nSafe = 100;
504 const std::size_t nCount = rLayouts.size();
505 std::size_t nIndex;
507 bool bConstrainsBroken = false;
511 bConstrainsBroken = false;
513 // first enforce minimum size constrains on all entities
514 for( nIndex = 0; nIndex < nCount; ++nIndex )
516 Layout& rLayout = rLayouts[nIndex];
517 if( rLayout.mnSize < rLayout.mnMinSize )
519 sal_Int32 nDiff(0);
520 bConstrainsBroken |= o3tl::checked_sub(rLayout.mnMinSize, rLayout.mnSize, nDiff);
521 bConstrainsBroken |= o3tl::checked_sub(nDistribute, nDiff, nDistribute);
522 rLayout.mnSize = rLayout.mnMinSize;
526 // calculate current width
527 // if nDistribute is < 0 (shrinking), entities that are already
528 // at minimum width are not counted
529 sal_Int32 nCurrentWidth = 0;
530 for( nIndex = 0; nIndex < nCount; ++nIndex )
532 Layout& rLayout = rLayouts[nIndex];
533 if( (nDistribute > 0) || (rLayout.mnSize > rLayout.mnMinSize) )
534 nCurrentWidth = o3tl::saturating_add(nCurrentWidth, rLayout.mnSize);
537 // now distribute over entities
538 if( (nCurrentWidth != 0) && (nDistribute != 0) )
540 sal_Int32 nDistributed = nDistribute;
541 for( nIndex = 0; nIndex < nCount; ++nIndex )
543 Layout& rLayout = rLayouts[nIndex];
544 if( (nDistribute > 0) || (rLayout.mnSize > rLayout.mnMinSize) )
546 sal_Int32 n(nDistributed); // for last entity use up rest
547 if (nIndex != (nCount-1))
549 bConstrainsBroken |= o3tl::checked_multiply(nDistribute, rLayout.mnSize, n);
550 n /= nCurrentWidth;
553 bConstrainsBroken |= o3tl::checked_add(rLayout.mnSize, n, rLayout.mnSize);
554 nDistributed -= n;
556 if( rLayout.mnSize < rLayout.mnMinSize )
557 bConstrainsBroken = true;
561 } while( bConstrainsBroken && --nSafe );
563 sal_Int32 nSize = 0;
564 for( nIndex = 0; nIndex < nCount; ++nIndex )
565 nSize = o3tl::saturating_add(nSize, rLayouts[nIndex].mnSize);
567 return nSize;
571 typedef std::vector< CellRef > MergeableCellVector;
572 typedef std::vector< MergeableCellVector > MergeVector;
575 void TableLayouter::LayoutTableWidth( tools::Rectangle& rArea, bool bFit )
577 const sal_Int32 nColCount = getColumnCount();
578 const sal_Int32 nRowCount = getRowCount();
579 if( nColCount == 0 )
580 return;
582 MergeVector aMergedCells( nColCount );
583 std::vector<sal_Int32> aOptimalColumns;
585 static constexpr OUStringLiteral sOptimalSize(u"OptimalSize");
587 if( sal::static_int_cast< sal_Int32 >( maColumns.size() ) != nColCount )
588 maColumns.resize( nColCount );
590 Reference< XTableColumns > xCols( mxTable->getColumns(), UNO_SET_THROW );
592 // first calculate current width and initial minimum width per column,
593 // merged cells will be counted later
594 sal_Int32 nCurrentWidth = 0;
595 sal_Int32 nCol = 0, nRow = 0;
596 for( nCol = 0; nCol < nColCount; nCol++ )
598 sal_Int32 nMinWidth = 0;
600 bool bIsEmpty = true; // check if all cells in this column are merged
602 for( nRow = 0; nRow < nRowCount; ++nRow )
604 CellRef xCell( getCell( CellPos( nCol, nRow ) ) );
605 if( xCell.is() && !xCell->isMerged() )
607 bIsEmpty = false;
609 sal_Int32 nColSpan = xCell->getColumnSpan();
610 if( nColSpan > 1 )
612 // merged cells will be evaluated later
613 aMergedCells[nCol+nColSpan-1].push_back( xCell );
615 else
617 nMinWidth = std::max( nMinWidth, xCell->getMinimumWidth() );
622 maColumns[nCol].mnMinSize = nMinWidth;
624 if( bIsEmpty )
626 maColumns[nCol].mnSize = 0;
628 else
630 sal_Int32 nColWidth = 0;
631 Reference< XPropertySet > xColSet( xCols->getByIndex( nCol ), UNO_QUERY_THROW );
632 bool bOptimal = false;
633 xColSet->getPropertyValue( sOptimalSize ) >>= bOptimal;
634 if( bOptimal )
636 aOptimalColumns.push_back(nCol);
638 else
640 xColSet->getPropertyValue( gsSize ) >>= nColWidth;
643 maColumns[nCol].mnSize = std::max( nColWidth, nMinWidth);
645 nCurrentWidth = o3tl::saturating_add(nCurrentWidth, maColumns[nCol].mnSize);
649 // if we have optimal sized rows, distribute what is given (left)
650 if( !bFit && !aOptimalColumns.empty() && (nCurrentWidth < rArea.getOpenWidth()) )
652 sal_Int32 nLeft = rArea.getOpenWidth() - nCurrentWidth;
653 sal_Int32 nDistribute = nLeft / aOptimalColumns.size();
655 auto iter( aOptimalColumns.begin() );
656 while( iter != aOptimalColumns.end() )
658 sal_Int32 nOptCol = *iter++;
659 if( iter == aOptimalColumns.end() )
660 nDistribute = nLeft;
662 maColumns[nOptCol].mnSize += nDistribute;
663 nLeft -= nDistribute;
666 DBG_ASSERT( nLeft == 0, "svx::TableLayouter::LayoutTableWidtht(), layouting failed!" );
669 // now check if merged cells fit
670 for( nCol = 1; nCol < nColCount; ++nCol )
672 bool bChanges = false;
674 const sal_Int32 nOldSize = maColumns[nCol].mnSize;
676 for( const CellRef& xCell : aMergedCells[nCol] )
678 sal_Int32 nMinWidth = xCell->getMinimumWidth();
680 for( sal_Int32 nMCol = nCol - xCell->getColumnSpan() + 1; (nMCol > 0) && (nMCol < nCol); ++nMCol )
681 nMinWidth -= maColumns[nMCol].mnSize;
683 if( nMinWidth > maColumns[nCol].mnMinSize )
684 maColumns[nCol].mnMinSize = nMinWidth;
686 if( nMinWidth > maColumns[nCol].mnSize )
688 maColumns[nCol].mnSize = nMinWidth;
689 bChanges = true;
693 if( bChanges )
695 nCurrentWidth = o3tl::saturating_add(nCurrentWidth, maColumns[nCol].mnSize - nOldSize);
699 // now scale if wanted and needed
700 if( bFit && (nCurrentWidth != rArea.getOpenWidth()) )
701 distribute( maColumns, rArea.getOpenWidth() - nCurrentWidth );
703 // last step, update left edges
704 sal_Int32 nNewWidth = 0;
706 const bool bRTL = (mxTable->getSdrTableObj()->GetWritingMode() == WritingMode_RL_TB);
707 RangeIterator<sal_Int32> coliter( 0, nColCount, !bRTL );
708 while( coliter.next(nCol ) )
710 maColumns[nCol].mnPos = nNewWidth;
711 nNewWidth = o3tl::saturating_add(nNewWidth, maColumns[nCol].mnSize);
712 if( bFit )
714 Reference< XPropertySet > xColSet( xCols->getByIndex(nCol), UNO_QUERY_THROW );
715 xColSet->setPropertyValue( gsSize, Any( maColumns[nCol].mnSize ) );
719 rArea.SetSize( Size( nNewWidth, rArea.GetHeight() ) );
720 updateCells( rArea );
724 void TableLayouter::LayoutTableHeight( tools::Rectangle& rArea, bool bFit )
726 const sal_Int32 nColCount = getColumnCount();
727 const sal_Int32 nRowCount = getRowCount();
729 if( nRowCount == 0 )
730 return;
732 Reference< XTableRows > xRows( mxTable->getRows() );
734 MergeVector aMergedCells( nRowCount );
735 std::vector<sal_Int32> aOptimalRows;
737 static constexpr OUStringLiteral sOptimalSize(u"OptimalSize");
739 // first calculate current height and initial minimum size per column,
740 // merged cells will be counted later
741 sal_Int32 nCurrentHeight = 0;
742 sal_Int32 nCol, nRow;
743 for( nRow = 0; nRow < nRowCount; ++nRow )
745 sal_Int32 nMinHeight = 0;
747 bool bIsEmpty = true; // check if all cells in this row are merged
749 for( nCol = 0; nCol < nColCount; ++nCol )
751 CellRef xCell( getCell( CellPos( nCol, nRow ) ) );
752 if( xCell.is() && !xCell->isMerged() )
754 bIsEmpty = false;
756 sal_Int32 nRowSpan = xCell->getRowSpan();
757 if( nRowSpan > 1 )
759 // merged cells will be evaluated later
760 aMergedCells[nRow+nRowSpan-1].push_back( xCell );
762 else
764 nMinHeight = std::max( nMinHeight, xCell->getMinimumHeight() );
769 maRows[nRow].mnMinSize = nMinHeight;
771 if( bIsEmpty )
773 maRows[nRow].mnSize = 0;
775 else
777 sal_Int32 nRowHeight = 0;
778 Reference<XPropertySet> xRowSet(xRows->getByIndex(nRow), UNO_QUERY_THROW);
780 bool bOptimal = false;
781 xRowSet->getPropertyValue( sOptimalSize ) >>= bOptimal;
782 if( bOptimal )
784 aOptimalRows.push_back( nRow );
786 else
788 xRowSet->getPropertyValue( gsSize ) >>= nRowHeight;
791 maRows[nRow].mnSize = nRowHeight;
793 if( maRows[nRow].mnSize < nMinHeight )
794 maRows[nRow].mnSize = nMinHeight;
796 nCurrentHeight = o3tl::saturating_add(nCurrentHeight, maRows[nRow].mnSize);
800 // if we have optimal sized rows, distribute what is given (left)
801 if( !bFit && !aOptimalRows.empty() && (nCurrentHeight < rArea.getOpenHeight()) )
803 sal_Int32 nLeft = rArea.getOpenHeight() - nCurrentHeight;
804 sal_Int32 nDistribute = nLeft / aOptimalRows.size();
806 auto iter( aOptimalRows.begin() );
807 while( iter != aOptimalRows.end() )
809 sal_Int32 nOptRow = *iter++;
810 if( iter == aOptimalRows.end() )
811 nDistribute = nLeft;
813 maRows[nOptRow].mnSize += nDistribute;
814 nLeft -= nDistribute;
818 DBG_ASSERT( nLeft == 0, "svx::TableLayouter::LayoutTableHeight(), layouting failed!" );
821 // now check if merged cells fit
822 for( nRow = 1; nRow < nRowCount; ++nRow )
824 bool bChanges = false;
825 sal_Int32 nOldSize = maRows[nRow].mnSize;
827 for( const CellRef& xCell : aMergedCells[nRow] )
829 sal_Int32 nMinHeight = xCell->getMinimumHeight();
831 for( sal_Int32 nMRow = nRow - xCell->getRowSpan() + 1; (nMRow > 0) && (nMRow < nRow); ++nMRow )
832 nMinHeight -= maRows[nMRow].mnSize;
834 if( nMinHeight > maRows[nRow].mnMinSize )
835 maRows[nRow].mnMinSize = nMinHeight;
837 if( nMinHeight > maRows[nRow].mnSize )
839 maRows[nRow].mnSize = nMinHeight;
840 bChanges = true;
843 if( bChanges )
844 nCurrentHeight = o3tl::saturating_add(nCurrentHeight, maRows[nRow].mnSize - nOldSize);
847 // now scale if wanted and needed
848 if( bFit && nCurrentHeight != rArea.getOpenHeight() )
849 distribute(maRows, o3tl::saturating_sub<sal_Int32>(rArea.getOpenHeight(), nCurrentHeight));
851 // last step, update left edges
852 sal_Int32 nNewHeight = 0;
853 for( nRow = 0; nRow < nRowCount; ++nRow )
855 maRows[nRow].mnPos = nNewHeight;
856 nNewHeight = o3tl::saturating_add(nNewHeight, maRows[nRow].mnSize);
858 if( bFit )
860 Reference< XPropertySet > xRowSet( xRows->getByIndex(nRow), UNO_QUERY_THROW );
861 xRowSet->setPropertyValue( gsSize, Any( maRows[nRow].mnSize ) );
865 rArea.SetSize( Size( rArea.GetWidth(), nNewHeight ) );
866 updateCells( rArea );
870 /** try to fit the table into the given rectangle.
871 If the rectangle is too small, it will be grown to fit the table. */
872 void TableLayouter::LayoutTable( tools::Rectangle& rRectangle, bool bFitWidth, bool bFitHeight )
874 if( !mxTable.is() )
875 return;
877 const sal_Int32 nRowCount = mxTable->getRowCount();
878 const sal_Int32 nColCount = mxTable->getColumnCount();
880 if( (nRowCount != getRowCount()) || (nColCount != getColumnCount()) )
882 if( static_cast< sal_Int32 >( maRows.size() ) != nRowCount )
883 maRows.resize( nRowCount );
885 for( sal_Int32 nRow = 0; nRow < nRowCount; nRow++ )
886 maRows[nRow].clear();
888 if( static_cast< sal_Int32 >( maColumns.size() ) != nColCount )
889 maColumns.resize( nColCount );
891 for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ )
892 maColumns[nCol].clear();
895 LayoutTableWidth( rRectangle, bFitWidth );
896 LayoutTableHeight( rRectangle, bFitHeight );
897 UpdateBorderLayout();
901 void TableLayouter::updateCells( tools::Rectangle const & rRectangle )
903 const sal_Int32 nColCount = getColumnCount();
904 const sal_Int32 nRowCount = getRowCount();
906 CellPos aPos;
907 for( aPos.mnRow = 0; aPos.mnRow < nRowCount; aPos.mnRow++ )
909 for( aPos.mnCol = 0; aPos.mnCol < nColCount; aPos.mnCol++ )
911 CellRef xCell( getCell( aPos ) );
912 if( xCell.is() )
914 basegfx::B2IRectangle aCellArea;
915 if( getCellArea( xCell, aPos, aCellArea ) )
917 tools::Rectangle aCellRect;
918 aCellRect.SetLeft( aCellArea.getMinX() );
919 aCellRect.SetRight( aCellArea.getMaxX() );
920 aCellRect.SetTop( aCellArea.getMinY() );
921 aCellRect.SetBottom( aCellArea.getMaxY() );
922 aCellRect.Move( rRectangle.Left(), rRectangle.Top() );
923 xCell->setCellRect( aCellRect );
931 CellRef TableLayouter::getCell( const CellPos& rPos ) const
933 CellRef xCell;
934 if( mxTable.is() ) try
936 xCell = mxTable->getCell( rPos.mnCol, rPos.mnRow );
938 catch( Exception& )
940 TOOLS_WARN_EXCEPTION("svx", "");
942 return xCell;
946 bool TableLayouter::HasPriority( const SvxBorderLine* pThis, const SvxBorderLine* pOther )
948 if (!pThis || ((pThis == &gEmptyBorder) && (pOther != nullptr)))
949 return false;
950 if (!pOther || (pOther == &gEmptyBorder))
951 return true;
953 sal_uInt16 nThisSize = pThis->GetScaledWidth();
954 sal_uInt16 nOtherSize = pOther->GetScaledWidth();
956 if (nThisSize > nOtherSize)
957 return true;
959 else if (nThisSize < nOtherSize)
961 return false;
963 else
965 if ( pOther->GetInWidth() && !pThis->GetInWidth() )
967 return true;
969 else if ( pThis->GetInWidth() && !pOther->GetInWidth() )
971 return false;
973 else
975 return true; //! ???
980 void TableLayouter::SetBorder( sal_Int32 nCol, sal_Int32 nRow, bool bHorizontal, const SvxBorderLine* pLine )
982 if (!pLine)
983 pLine = &gEmptyBorder;
985 BorderLineMap& rMap = bHorizontal ? maHorizontalBorders : maVerticalBorders;
987 if( (nCol >= 0) && (nCol < sal::static_int_cast<sal_Int32>(rMap.size())) &&
988 (nRow >= 0) && (nRow < sal::static_int_cast<sal_Int32>(rMap[nCol].size())) )
990 SvxBorderLine *pOld = rMap[nCol][nRow];
992 if (HasPriority(pLine, pOld))
994 if (pOld && pOld != &gEmptyBorder)
995 delete pOld;
997 SvxBorderLine* pNew = (pLine != &gEmptyBorder) ? new SvxBorderLine(*pLine) : &gEmptyBorder;
999 rMap[nCol][nRow] = pNew;
1002 else
1004 OSL_FAIL( "sdr::table::TableLayouter::SetBorder(), invalid border!" );
1008 void TableLayouter::ClearBorderLayout()
1010 ClearBorderLayout(maHorizontalBorders);
1011 ClearBorderLayout(maVerticalBorders);
1014 void TableLayouter::ClearBorderLayout(BorderLineMap& rMap)
1016 const sal_Int32 nColCount = rMap.size();
1018 for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ )
1020 const sal_Int32 nRowCount = rMap[nCol].size();
1021 for( sal_Int32 nRow = 0; nRow < nRowCount; nRow++ )
1023 SvxBorderLine* pLine = rMap[nCol][nRow];
1024 if( pLine )
1026 if( pLine != &gEmptyBorder )
1027 delete pLine;
1029 rMap[nCol][nRow] = nullptr;
1035 void TableLayouter::ResizeBorderLayout()
1037 ClearBorderLayout();
1038 ResizeBorderLayout(maHorizontalBorders);
1039 ResizeBorderLayout(maVerticalBorders);
1043 void TableLayouter::ResizeBorderLayout( BorderLineMap& rMap )
1045 const sal_Int32 nColCount = getColumnCount() + 1;
1046 const sal_Int32 nRowCount = getRowCount() + 1;
1048 if( sal::static_int_cast<sal_Int32>(rMap.size()) != nColCount )
1049 rMap.resize( nColCount );
1051 for( auto& nCol : rMap )
1053 if( sal::static_int_cast<sal_Int32>(nCol.size()) != nRowCount )
1054 nCol.resize( nRowCount );
1059 void TableLayouter::UpdateBorderLayout()
1061 // make sure old border layout is cleared and border maps have correct size
1062 ResizeBorderLayout();
1064 const sal_Int32 nColCount = getColumnCount();
1065 const sal_Int32 nRowCount = getRowCount();
1067 CellPos aPos;
1068 for( aPos.mnRow = 0; aPos.mnRow < nRowCount; aPos.mnRow++ )
1070 for( aPos.mnCol = 0; aPos.mnCol < nColCount; aPos.mnCol++ )
1072 CellRef xCell( getCell( aPos ) );
1073 if( !xCell.is() )
1074 continue;
1076 const SvxBoxItem* pThisAttr = xCell->GetItemSet().GetItem<SvxBoxItem>( SDRATTR_TABLE_BORDER );
1077 OSL_ENSURE(pThisAttr,"sdr::table::TableLayouter::UpdateBorderLayout(), no border attribute?");
1079 if( !pThisAttr )
1080 continue;
1082 const sal_Int32 nLastRow = xCell->getRowSpan() + aPos.mnRow;
1083 const sal_Int32 nLastCol = xCell->getColumnSpan() + aPos.mnCol;
1085 for( sal_Int32 nRow = aPos.mnRow; nRow < nLastRow; nRow++ )
1087 SetBorder( aPos.mnCol, nRow, false, pThisAttr->GetLeft() );
1088 SetBorder( nLastCol, nRow, false, pThisAttr->GetRight() );
1091 for( sal_Int32 nCol = aPos.mnCol; nCol < nLastCol; nCol++ )
1093 SetBorder( nCol, aPos.mnRow, true, pThisAttr->GetTop() );
1094 SetBorder( nCol, nLastRow, true, pThisAttr->GetBottom() );
1101 void TableLayouter::DistributeColumns( ::tools::Rectangle& rArea,
1102 sal_Int32 nFirstCol,
1103 sal_Int32 nLastCol,
1104 const bool bOptimize,
1105 const bool bMinimize )
1107 if( !mxTable.is() )
1108 return;
1112 const sal_Int32 nColCount = getColumnCount();
1113 Reference< XTableColumns > xCols( mxTable->getColumns(), UNO_SET_THROW );
1114 const Size aSize(0xffffff, 0xffffff);
1116 //special case - optimize a single column
1117 if ( (bOptimize || bMinimize) && nFirstCol == nLastCol )
1119 const sal_Int32 nWish = calcPreferredColumnWidth(nFirstCol, aSize);
1120 if ( nWish < getColumnWidth(nFirstCol) )
1122 Reference< XPropertySet > xColSet( xCols->getByIndex(nFirstCol), UNO_QUERY_THROW );
1123 xColSet->setPropertyValue( gsSize, Any( nWish ) );
1125 //FitWidth automatically distributes the new excess space
1126 LayoutTable( rArea, /*bFitWidth=*/!bMinimize, /*bFitHeight=*/false );
1130 if( (nFirstCol < 0) || (nFirstCol>= nLastCol) || (nLastCol >= nColCount) )
1131 return;
1133 sal_Int32 nAllWidth = 0;
1134 float fAllWish = 0;
1135 sal_Int32 nUnused = 0;
1136 std::vector<sal_Int32> aWish(nColCount);
1138 for( sal_Int32 nCol = nFirstCol; nCol <= nLastCol; ++nCol )
1139 nAllWidth += getColumnWidth(nCol);
1141 const sal_Int32 nEqualWidth = nAllWidth / (nLastCol-nFirstCol+1);
1143 //pass 1 - collect unneeded space (from an equal width perspective)
1144 if ( bMinimize || bOptimize )
1146 for( sal_Int32 nCol = nFirstCol; nCol <= nLastCol; ++nCol )
1148 const sal_Int32 nIndex = nCol - nFirstCol;
1149 aWish[nIndex] = calcPreferredColumnWidth(nCol, aSize);
1150 fAllWish += aWish[nIndex];
1151 if ( aWish[nIndex] < nEqualWidth )
1152 nUnused += nEqualWidth - aWish[nIndex];
1155 const sal_Int32 nDistributeExcess = nAllWidth - fAllWish;
1157 sal_Int32 nWidth = nEqualWidth;
1158 for( sal_Int32 nCol = nFirstCol; nCol <= nLastCol; ++nCol )
1160 if ( !bMinimize && nCol == nLastCol )
1161 nWidth = nAllWidth; // last column gets rounding/logic errors
1162 else if ( (bMinimize || bOptimize) && fAllWish )
1164 //pass 2 - first come, first served when requesting from the
1165 // unneeded pool, or proportionally allocate excess.
1166 const sal_Int32 nIndex = nCol - nFirstCol;
1167 if ( aWish[nIndex] > nEqualWidth + nUnused )
1169 nWidth = nEqualWidth + nUnused;
1170 nUnused = 0;
1172 else
1174 nWidth = aWish[nIndex];
1175 if ( aWish[nIndex] > nEqualWidth )
1176 nUnused -= aWish[nIndex] - nEqualWidth;
1178 if ( !bMinimize && nDistributeExcess > 0 )
1179 nWidth += nWidth / fAllWish * nDistributeExcess;
1183 Reference< XPropertySet > xColSet( xCols->getByIndex( nCol ), UNO_QUERY_THROW );
1184 xColSet->setPropertyValue( gsSize, Any( nWidth ) );
1186 nAllWidth -= nWidth;
1189 LayoutTable( rArea, !bMinimize, false );
1191 catch( Exception& )
1193 TOOLS_WARN_EXCEPTION("svx", "");
1198 void TableLayouter::DistributeRows( ::tools::Rectangle& rArea,
1199 sal_Int32 nFirstRow,
1200 sal_Int32 nLastRow,
1201 const bool bOptimize,
1202 const bool bMinimize )
1204 if( !mxTable.is() )
1205 return;
1209 const sal_Int32 nRowCount = mxTable->getRowCount();
1210 Reference< XTableRows > xRows( mxTable->getRows(), UNO_SET_THROW );
1211 sal_Int32 nMinHeight = 0;
1213 //special case - minimize a single row
1214 if ( bMinimize && nFirstRow == nLastRow )
1216 const sal_Int32 nWish = std::max( maRows[nFirstRow].mnMinSize, nMinHeight );
1217 if ( nWish < getRowHeight(nFirstRow) )
1219 Reference< XPropertySet > xRowSet( xRows->getByIndex( nFirstRow ), UNO_QUERY_THROW );
1220 xRowSet->setPropertyValue( gsSize, Any( nWish ) );
1222 LayoutTable( rArea, /*bFitWidth=*/false, /*bFitHeight=*/!bMinimize );
1226 if( (nFirstRow < 0) || (nFirstRow>= nLastRow) || (nLastRow >= nRowCount) )
1227 return;
1229 sal_Int32 nAllHeight = 0;
1230 sal_Int32 nMaxHeight = 0;
1232 for( sal_Int32 nRow = nFirstRow; nRow <= nLastRow; ++nRow )
1234 nMinHeight = std::max( maRows[nRow].mnMinSize, nMinHeight );
1235 nMaxHeight = std::max( maRows[nRow].mnSize, nMaxHeight );
1236 nAllHeight += maRows[nRow].mnSize;
1239 const sal_Int32 nRows = nLastRow-nFirstRow+1;
1240 sal_Int32 nHeight = nAllHeight / nRows;
1242 if ( !bMinimize && nHeight < nMaxHeight )
1244 if ( !bOptimize )
1246 sal_Int32 nNeededHeight = nRows * nMaxHeight;
1247 rArea.AdjustBottom(nNeededHeight - nAllHeight );
1248 nHeight = nMaxHeight;
1249 nAllHeight = nRows * nMaxHeight;
1251 else if ( nHeight < nMinHeight )
1253 sal_Int32 nNeededHeight = nRows * nMinHeight;
1254 rArea.AdjustBottom(nNeededHeight - nAllHeight );
1255 nHeight = nMinHeight;
1256 nAllHeight = nRows * nMinHeight;
1260 for( sal_Int32 nRow = nFirstRow; nRow <= nLastRow; ++nRow )
1262 if ( bMinimize )
1263 nHeight = maRows[nRow].mnMinSize;
1264 else if ( nRow == nLastRow )
1265 nHeight = nAllHeight; // last row get round errors
1267 Reference< XPropertySet > xRowSet( xRows->getByIndex( nRow ), UNO_QUERY_THROW );
1268 xRowSet->setPropertyValue( gsSize, Any( nHeight ) );
1270 nAllHeight -= nHeight;
1273 LayoutTable( rArea, false, !bMinimize );
1275 catch( Exception& )
1277 TOOLS_WARN_EXCEPTION("svx", "");
1281 void TableLayouter::dumpAsXml(xmlTextWriterPtr pWriter) const
1283 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("TableLayouter"));
1285 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("columns"));
1286 for (const auto& rColumn : maColumns)
1287 rColumn.dumpAsXml(pWriter);
1288 (void)xmlTextWriterEndElement(pWriter);
1290 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("rows"));
1291 for (const auto& rRow : maRows)
1292 rRow.dumpAsXml(pWriter);
1293 (void)xmlTextWriterEndElement(pWriter);
1295 (void)xmlTextWriterEndElement(pWriter);
1298 void TableLayouter::Layout::dumpAsXml(xmlTextWriterPtr pWriter) const
1300 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("TableLayouter_Layout"));
1302 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("pos"), BAD_CAST(OString::number(mnPos).getStr()));
1303 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("size"), BAD_CAST(OString::number(mnSize).getStr()));
1304 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("minSize"), BAD_CAST(OString::number(mnMinSize).getStr()));
1306 (void)xmlTextWriterEndElement(pWriter);
1311 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */