diff options
Diffstat (limited to 'sw/source/core/table')
-rw-r--r-- | sw/source/core/table/swnewtable.cxx | 2199 | ||||
-rw-r--r-- | sw/source/core/table/swtable.cxx | 2771 |
2 files changed, 4970 insertions, 0 deletions
diff --git a/sw/source/core/table/swnewtable.cxx b/sw/source/core/table/swnewtable.cxx new file mode 100644 index 000000000..d1e724936 --- /dev/null +++ b/sw/source/core/table/swnewtable.cxx @@ -0,0 +1,2199 @@ +/* -*- 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 <swtable.hxx> +#include <tblsel.hxx> +#include <tblrwcl.hxx> +#include <ndtxt.hxx> +#include <node.hxx> +#include <UndoTable.hxx> +#include <pam.hxx> +#include <frmfmt.hxx> +#include <frmatr.hxx> +#include <cellfrm.hxx> +#include <fmtfsize.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentContentOperations.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <cstdlib> +#include <vector> +#include <set> +#include <list> +#include <memory> +#include <editeng/boxitem.hxx> +#include <editeng/protitem.hxx> +#include <swtblfmt.hxx> +#include <calbck.hxx> +#include <sal/log.hxx> + +#ifdef DBG_UTIL +#define CHECK_TABLE(t) (t).CheckConsistency(); +#else +#define CHECK_TABLE(t) +#endif + +/** SwBoxSelection is a small helperclass (structure) to handle selections + of cells (boxes) between table functions + + It contains an "array" of table boxes, a rectangulare selection of table boxes. + To be more specific, it contains a vector of box selections, + every box selection (SwSelBoxes) contains the selected boxes inside one row. + The member mnMergeWidth contains the width of the selected boxes +*/ + +class SwBoxSelection +{ +public: + std::vector<SwSelBoxes> maBoxes; + long mnMergeWidth; + SwBoxSelection() : mnMergeWidth(0) {} + bool isEmpty() const { return maBoxes.empty(); } + void push_back(const SwSelBoxes& rNew) { maBoxes.push_back(rNew); } +}; + +/** NewMerge(..) removes the superfluous cells after cell merge + +SwTable::NewMerge(..) does some cleaning up, +it simply deletes the superfluous cells ("cell span") +and notifies the Undo about it. +The main work has been done by SwTable::PrepareMerge(..) already. + +@param rBoxes +the boxes to remove + +@param pUndo +the undo object to notify, maybe empty + +@return true for compatibility reasons with OldMerge(..) +*/ + +bool SwTable::NewMerge( SwDoc* pDoc, const SwSelBoxes& rBoxes, + const SwSelBoxes& rMerged, SwUndoTableMerge* pUndo ) +{ + if( pUndo ) + pUndo->SetSelBoxes( rBoxes ); + DeleteSel( pDoc, rBoxes, &rMerged, nullptr, true, true ); + + CHECK_TABLE( *this ) + return true; +} + +/** lcl_CheckMinMax helps evaluating (horizontal) min/max of boxes + +lcl_CheckMinMax(..) compares the left border and the right border +of a given cell with the given range and sets it accordingly. + +@param rMin +will be decremented if necessary to the left border of the cell + +@param rMax +will be incremented if necessary to the right border of the cell + +@param rLine +the row (table line) of the interesting box + +@param nCheck +the index of the box in the table box array of the given row + +@param bSet +if bSet is false, rMin and rMax will be manipulated if necessary +if bSet is true, rMin and rMax will be set to the left and right border of the box + +*/ + +static void lcl_CheckMinMax( long& rMin, long& rMax, const SwTableLine& rLine, size_t nCheck, bool bSet ) +{ + ++nCheck; + if( rLine.GetTabBoxes().size() < nCheck ) + { // robust + OSL_FAIL( "Box out of table line" ); + nCheck = rLine.GetTabBoxes().size(); + } + + long nNew = 0; // will be the right border of the current box + long nWidth = 0; // the width of the current box + for( size_t nCurrBox = 0; nCurrBox < nCheck; ++nCurrBox ) + { + SwTableBox* pBox = rLine.GetTabBoxes()[nCurrBox]; + OSL_ENSURE( pBox, "Missing table box" ); + nWidth = pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + nNew += nWidth; + } + // nNew is the right border of the wished box + if( bSet || nNew > rMax ) + rMax = nNew; + nNew -= nWidth; // nNew becomes the left border of the wished box + if( bSet || nNew < rMin ) + rMin = nNew; +} + +/** lcl_Box2LeftBorder(..) delivers the left (logical) border of a table box + +The left logical border of a table box is the sum of the cell width before this +box. + +@param rBox +is the requested table box + +@return is the left logical border (long, even it cannot be negative) + +*/ + +static long lcl_Box2LeftBorder( const SwTableBox& rBox ) +{ + if( !rBox.GetUpper() ) + return 0; + long nLeft = 0; + const SwTableLine &rLine = *rBox.GetUpper(); + const size_t nCount = rLine.GetTabBoxes().size(); + for( size_t nCurrBox = 0; nCurrBox < nCount; ++nCurrBox ) + { + SwTableBox* pBox = rLine.GetTabBoxes()[nCurrBox]; + OSL_ENSURE( pBox, "Missing table box" ); + if( pBox == &rBox ) + return nLeft; + nLeft += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + } + OSL_FAIL( "Box not found in own upper?" ); + return nLeft; +} + +/** lcl_LeftBorder2Box delivers the box to a given left border + +It's used to find the master/follow table boxes in previous/next rows. +Don't call this function to check if there is such a box, +call it if you know there has to be such box. + +@param nLeft +the left border (logical x-value) of the demanded box + +@param rLine +the row (table line) to be scanned + +@return a pointer to the table box inside the given row with the wished left border + +*/ + +static SwTableBox* lcl_LeftBorder2Box( long nLeft, const SwTableLine* pLine ) +{ + if( !pLine ) + return nullptr; + long nCurrLeft = 0; + const size_t nCount = pLine->GetTabBoxes().size(); + for( size_t nCurrBox = 0; nCurrBox < nCount; ++nCurrBox ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrBox]; + OSL_ENSURE( pBox, "Missing table box" ); + if( pBox->GetFrameFormat()->GetFrameSize().GetWidth() ) + { + if( nCurrLeft == nLeft ) + return pBox; + // HACK: It appears that rounding errors may result in positions not matching + // exactly, so allow a little tolerance. This happens at least with merged cells + // in the doc from fdo#38414 . + if( std::abs( nCurrLeft - nLeft ) <= ( nLeft / 1000 )) + return pBox; + if( nCurrLeft >= nLeft ) + { + SAL_WARN( "sw.core", "Possibly wrong box found" ); + return pBox; + } + } + nCurrLeft += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + } + OSL_FAIL( "Didn't find wished box" ); + return nullptr; +} + +/** lcl_ChangeRowSpan corrects row span after insertion/deletion of rows + +lcl_ChangeRowSpan(..) has to be called after an insertion or deletion of rows +to adjust the row spans of previous rows accordingly. +If rows are deleted, the previous rows with row spans into the deleted area +have to be decremented by the number of _overlapped_ inserted rows. +If rows are inserted, the previous rows with row span into the inserted area +have to be incremented by the number of inserted rows. +For those row spans which ends exactly above the inserted area it has to be +decided by the parameter bSingle if they have to be expanded or not. + +@param rTable +the table to manipulate (has to be a new model table) + +@param nDiff +the number of rows which has been inserted (nDiff > 0) or deleted (nDiff < 0) + +@param nRowIdx +the index of the first row which has to be checked + +@param bSingle +true if the new inserted row should not extend row spans which ends in the row above +this is for rows inserted by UI "insert row" +false if all cells of an inserted row has to be overlapped by the previous row +this is for rows inserted by "split row" +false is also needed for deleted rows + +*/ + +static void lcl_ChangeRowSpan( const SwTable& rTable, const long nDiff, + sal_uInt16 nRowIdx, const bool bSingle ) +{ + if( !nDiff || nRowIdx >= rTable.GetTabLines().size() ) + return; + OSL_ENSURE( !bSingle || nDiff > 0, "Don't set bSingle when deleting lines!" ); + bool bGoOn; + // nDistance is the distance between the current row and the critical row, + // e.g. the deleted rows or the inserted rows. + // If the row span is lower than the distance there is nothing to do + // because the row span ends before the critical area. + // When the inserted rows should not be overlapped by row spans which ends + // exactly in the row above, the trick is to start with a distance of 1. + long nDistance = bSingle ? 1 : 0; + do + { + bGoOn = false; // will be set to true if we found a non-master cell + // which has to be manipulated => we have to check the previous row, too. + const SwTableLine* pLine = rTable.GetTabLines()[ nRowIdx ]; + const size_t nBoxCount = pLine->GetTabBoxes().size(); + for( size_t nCurrBox = 0; nCurrBox < nBoxCount; ++nCurrBox ) + { + long nRowSpan = pLine->GetTabBoxes()[nCurrBox]->getRowSpan(); + long nAbsSpan = nRowSpan > 0 ? nRowSpan : -nRowSpan; + // Check if the last overlapped cell is above or below + // the critical area + if( nAbsSpan > nDistance ) + { + if( nDiff > 0 ) + { + if( nRowSpan > 0 ) + nRowSpan += nDiff; // increment row span of master cell + else + { + nRowSpan -= nDiff; // increment row span of non-master cell + bGoOn = true; + } + } + else + { + if( nRowSpan > 0 ) + { // A master cell + // end of row span behind the deleted area .. + if( nRowSpan - nDistance > -nDiff ) + nRowSpan += nDiff; + else // .. or inside the deleted area + nRowSpan = nDistance + 1; + } + else + { // Same for a non-master cell + if( nRowSpan + nDistance < nDiff ) + nRowSpan -= nDiff; + else + nRowSpan = -nDistance - 1; + bGoOn = true; // We have to continue + } + } + pLine->GetTabBoxes()[ nCurrBox ]->setRowSpan( nRowSpan ); + } + } + ++nDistance; + if( nRowIdx ) + --nRowIdx; + else + bGoOn = false; //robust + } while( bGoOn ); +} + +/** CollectBoxSelection(..) create a rectangulare selection based on the given SwPaM + and prepares the selected cells for merging +*/ + +std::unique_ptr<SwBoxSelection> SwTable::CollectBoxSelection( const SwPaM& rPam ) const +{ + OSL_ENSURE( m_bNewModel, "Don't call me for old tables" ); + if( m_aLines.empty() ) + return nullptr; + const SwNode* pStartNd = rPam.Start()->nNode.GetNode().FindTableBoxStartNode(); + const SwNode* pEndNd = rPam.End()->nNode.GetNode().FindTableBoxStartNode(); + if( !pStartNd || !pEndNd || pStartNd == pEndNd ) + return nullptr; + + const size_t nLines = m_aLines.size(); + size_t nTop = 0; + size_t nBottom = 0; + long nMin = 0, nMax = 0; + int nFound = 0; + for( size_t nRow = 0; nFound < 2 && nRow < nLines; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + OSL_ENSURE( pLine, "Missing table line" ); + const size_t nCols = pLine->GetTabBoxes().size(); + for( size_t nCol = 0; nCol < nCols; ++nCol ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCol]; + OSL_ENSURE( pBox, "Missing table box" ); + if( nFound ) + { + if( pBox->GetSttNd() == pEndNd ) + { + nBottom = nRow; + lcl_CheckMinMax( nMin, nMax, *pLine, nCol, false ); + ++nFound; + break; + } + } + else if( pBox->GetSttNd() == pStartNd ) + { + nTop = nRow; + lcl_CheckMinMax( nMin, nMax, *pLine, nCol, true ); + ++nFound; + } + } + } + if( nFound < 2 ) + return nullptr; + + bool bOkay = true; + long nMid = ( nMin + nMax ) / 2; + + auto pRet(std::make_unique<SwBoxSelection>()); + std::vector< std::pair< SwTableBox*, long > > aNewWidthVector; + size_t nCheckBottom = nBottom; + long nLeftSpan = 0; + long nRightSpan = 0; + long nLeftSpanCnt = 0; + long nRightSpanCnt = 0; + for( size_t nRow = nTop; nRow <= nBottom && bOkay && nRow < nLines; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + OSL_ENSURE( pLine, "Missing table line" ); + SwSelBoxes aBoxes; + long nRight = 0; + const size_t nCount = pLine->GetTabBoxes().size(); + for( size_t nCurrBox = 0; nCurrBox < nCount; ++nCurrBox ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrBox]; + OSL_ENSURE( pBox, "Missing table box" ); + long nLeft = nRight; + nRight += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + long nRowSpan = pBox->getRowSpan(); + if( nRight <= nMin ) + { + if( nRight == nMin && nLeftSpanCnt ) + bOkay = false; + continue; + } + SwTableBox* pInnerBox = nullptr; + SwTableBox* pLeftBox = nullptr; + SwTableBox* pRightBox = nullptr; + long nDiff = 0; + long nDiff2 = 0; + if( nLeft < nMin ) + { + if( nRight >= nMid || nRight + nLeft >= nMin + nMin ) + { + if( nCurrBox ) + { + aBoxes.insert(pBox); + pInnerBox = pBox; + pLeftBox = pLine->GetTabBoxes()[nCurrBox-1]; + nDiff = nMin - nLeft; + if( nRight > nMax ) + { + if( nCurrBox+1 < nCount ) + { + pRightBox = pLine->GetTabBoxes()[nCurrBox+1]; + nDiff2 = nRight - nMax; + } + else + bOkay = false; + } + else if( nRightSpanCnt && nRight == nMax ) + bOkay = false; + } + else + bOkay = false; + } + else if( nCurrBox+1 < nCount ) + { + pLeftBox = pBox; + pInnerBox = pLine->GetTabBoxes()[nCurrBox+1]; + nDiff = nMin - nRight; + } + else + bOkay = false; + } + else if( nRight <= nMax ) + { + aBoxes.insert(pBox); + if( nRow == nTop && nRowSpan < 0 ) + { + bOkay = false; + break; + } + if( nRowSpan > 1 && nRow + nRowSpan - 1 > nBottom ) + nBottom = nRow + nRowSpan - 1; + if( nRowSpan < -1 && nRow - nRowSpan - 1 > nBottom ) + nBottom = nRow - nRowSpan - 1; + if( nRightSpanCnt && nRight == nMax ) + bOkay = false; + } + else if( nLeft < nMax ) + { + if( nLeft <= nMid || nRight + nLeft <= nMax ) + { + if( nCurrBox+1 < nCount ) + { + aBoxes.insert(pBox); + pInnerBox = pBox; + pRightBox = pLine->GetTabBoxes()[nCurrBox+1]; + nDiff = nRight - nMax; + } + else + bOkay = false; + } + else if( nCurrBox ) + { + pRightBox = pBox; + pInnerBox = pLine->GetTabBoxes()[nCurrBox-1]; + nDiff = nLeft - nMax; + } + else + bOkay = false; + } + else + break; + if( pInnerBox ) + { + if( nRow == nBottom ) + { + long nTmpSpan = pInnerBox->getRowSpan(); + if( nTmpSpan > 1 ) + nBottom += nTmpSpan - 1; + else if( nTmpSpan < -1 ) + nBottom -= nTmpSpan + 1; + } + SwTableBox* pOuterBox = pLeftBox; + do + { + if( pOuterBox ) + { + long nOutSpan = pOuterBox->getRowSpan(); + if( nOutSpan != 1 ) + { + size_t nCheck = nRow; + if( nOutSpan < 0 ) + { + const SwTableBox& rBox = + pOuterBox->FindStartOfRowSpan( *this ); + nOutSpan = rBox.getRowSpan(); + const SwTableLine* pTmpL = rBox.GetUpper(); + nCheck = GetTabLines().GetPos( pTmpL ); + if( nCheck < nTop ) + bOkay = false; + if( pOuterBox == pLeftBox ) + { + if( !nLeftSpanCnt || nMin - nDiff != nLeftSpan ) + bOkay = false; + } + else + { + if( !nRightSpanCnt || nMax + nDiff != nRightSpan ) + bOkay = false; + } + } + else + { + if( pOuterBox == pLeftBox ) + { + if( nLeftSpanCnt ) + bOkay = false; + nLeftSpan = nMin - nDiff; + nLeftSpanCnt = nOutSpan; + } + else + { + if( nRightSpanCnt ) + bOkay = false; + nRightSpan = nMax + nDiff; + nRightSpanCnt = nOutSpan; + } + } + nCheck += nOutSpan - 1; + if( nCheck > nCheckBottom ) + nCheckBottom = nCheck; + } + else if( ( nLeftSpanCnt && pLeftBox == pOuterBox ) || + ( nRightSpanCnt && pRightBox == pOuterBox ) ) + bOkay = false; + std::pair< SwTableBox*, long > aTmp; + aTmp.first = pInnerBox; + aTmp.second = -nDiff; + aNewWidthVector.push_back(aTmp); + aTmp.first = pOuterBox; + aTmp.second = nDiff; + aNewWidthVector.push_back(aTmp); + } + pOuterBox = pOuterBox == pRightBox ? nullptr : pRightBox; + if( nDiff2 ) + nDiff = nDiff2; + } while( pOuterBox ); + } + } + if( nLeftSpanCnt ) + --nLeftSpanCnt; + if( nRightSpanCnt ) + --nRightSpanCnt; + pRet->push_back(aBoxes); + } + if( nCheckBottom > nBottom ) + bOkay = false; + if( bOkay ) + { + pRet->mnMergeWidth = nMax - nMin; + for (auto const& newWidth : aNewWidthVector) + { + SwFrameFormat* pFormat = newWidth.first->ClaimFrameFormat(); + long nNewWidth = pFormat->GetFrameSize().GetWidth() + newWidth.second; + pFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nNewWidth, 0 ) ); + } + } + else + pRet.reset(); + + return pRet; +} + +/** lcl_InvalidateCellFrame(..) invalidates all layout representations of a given cell + to initiate a reformatting +*/ + +static void lcl_InvalidateCellFrame( const SwTableBox& rBox ) +{ + SwIterator<SwCellFrame,SwFormat> aIter( *rBox.GetFrameFormat() ); + for( SwCellFrame* pCell = aIter.First(); pCell; pCell = aIter.Next() ) + { + if( pCell->GetTabBox() == &rBox ) + { + pCell->InvalidateSize(); + SwFrame* pLower = pCell->GetLower(); + if( pLower ) + pLower->InvalidateSize_(); + } + } +} + +/** lcl_InsertPosition(..) evaluates the insert positions in every table line, + when a selection of cells is given and returns the average cell widths +*/ + +static long lcl_InsertPosition( SwTable &rTable, std::vector<sal_uInt16>& rInsPos, + const SwSelBoxes& rBoxes, bool bBehind ) +{ + sal_Int32 nAddWidth = 0; + long nCount = 0; + for (size_t j = 0; j < rBoxes.size(); ++j) + { + SwTableBox *pBox = rBoxes[j]; + SwTableLine* pLine = pBox->GetUpper(); + long nWidth = rBoxes[j]->GetFrameFormat()->GetFrameSize().GetWidth(); + nAddWidth += nWidth; + sal_uInt16 nCurrBox = pLine->GetBoxPos( pBox ); + sal_uInt16 nCurrLine = rTable.GetTabLines().GetPos( pLine ); + OSL_ENSURE( nCurrLine != USHRT_MAX, "Time to say Good-Bye.." ); + if( rInsPos[ nCurrLine ] == USHRT_MAX ) + { + rInsPos[ nCurrLine ] = nCurrBox; + ++nCount; + } + else if( ( rInsPos[ nCurrLine ] > nCurrBox ) == !bBehind ) + rInsPos[ nCurrLine ] = nCurrBox; + } + if( nCount ) + nAddWidth /= nCount; + return nAddWidth; +} + +/** SwTable::NewInsertCol(..) insert new column(s) into a table + +@param pDoc +the document + +@param rBoxes +the selected boxes + +@param nCnt +the number of columns to insert + +@param bBehind +insertion behind (true) or before (false) the selected boxes + +@return true, if any insertion has been done successfully + +*/ + +bool SwTable::NewInsertCol( SwDoc* pDoc, const SwSelBoxes& rBoxes, + sal_uInt16 nCnt, bool bBehind ) +{ + if( m_aLines.empty() || !nCnt ) + return false; + + CHECK_TABLE( *this ) + long nNewBoxWidth = 0; + std::vector< sal_uInt16 > aInsPos( m_aLines.size(), USHRT_MAX ); + { // Calculation of the insert positions and the width of the new boxes + sal_uInt64 nTableWidth = 0; + for( size_t i = 0; i < m_aLines[0]->GetTabBoxes().size(); ++i ) + nTableWidth += m_aLines[0]->GetTabBoxes()[i]->GetFrameFormat()->GetFrameSize().GetWidth(); + + // Fill the vector of insert positions and the (average) width to insert + sal_uInt64 nAddWidth = lcl_InsertPosition( *this, aInsPos, rBoxes, bBehind ); + + // Given is the (average) width of the selected boxes, if we would + // insert nCnt of columns the table would grow + // So we will shrink the table first, then insert the new boxes and + // get a table with the same width than before. + // But we will not shrink the table by the full already calculated value, + // we will reduce this value proportional to the old table width + nAddWidth *= nCnt; // we have to insert nCnt boxes per line + sal_uInt64 nResultingWidth = nAddWidth + nTableWidth; + if( !nResultingWidth ) + return false; + nAddWidth = (nAddWidth * nTableWidth) / nResultingWidth; + nNewBoxWidth = long( nAddWidth / nCnt ); // Rounding + nAddWidth = nNewBoxWidth * nCnt; // Rounding + if( !nAddWidth || nAddWidth >= nTableWidth ) + return false; + AdjustWidths( static_cast< long >(nTableWidth), static_cast< long >(nTableWidth - nAddWidth) ); + } + + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( rBoxes, *this ); + aFndBox.DelFrames( *this ); + + SwTableNode* pTableNd = GetTableNode(); + std::vector<SwTableBoxFormat*> aInsFormat( nCnt, nullptr ); + size_t nLastLine = SAL_MAX_SIZE; + long nLastRowSpan = 1; + + for( size_t i = 0; i < m_aLines.size(); ++i ) + { + SwTableLine* pLine = m_aLines[ i ]; + sal_uInt16 nInsPos = aInsPos[i]; + assert(nInsPos != USHRT_MAX); // didn't find insert position + SwTableBox* pBox = pLine->GetTabBoxes()[ nInsPos ]; + if( bBehind ) + ++nInsPos; + SwTableBoxFormat* pBoxFrameFormat = static_cast<SwTableBoxFormat*>(pBox->GetFrameFormat()); + ::InsTableBox( pDoc, pTableNd, pLine, pBoxFrameFormat, pBox, nInsPos, nCnt ); + long nRowSpan = pBox->getRowSpan(); + long nDiff = i - nLastLine; + bool bNewSpan = false; + if( nLastLine != SAL_MAX_SIZE && nDiff <= nLastRowSpan && + nRowSpan != nDiff - nLastRowSpan ) + { + bNewSpan = true; + while( nLastLine < i ) + { + SwTableLine* pTmpLine = m_aLines[ nLastLine ]; + sal_uInt16 nTmpPos = aInsPos[nLastLine]; + if( bBehind ) + ++nTmpPos; + for( sal_uInt16 j = 0; j < nCnt; ++j ) + pTmpLine->GetTabBoxes()[nTmpPos+j]->setRowSpan( nDiff ); + if( nDiff > 0 ) + nDiff = -nDiff; + ++nDiff; + ++nLastLine; + } + } + if( nRowSpan > 0 ) + bNewSpan = true; + if( bNewSpan ) + { + nLastLine = i; + if( nRowSpan < 0 ) + nLastRowSpan = -nRowSpan; + else + nLastRowSpan = nRowSpan; + } + const SvxBoxItem& aSelBoxItem = pBoxFrameFormat->GetBox(); + std::unique_ptr<SvxBoxItem> pNoRightBorder; + if( aSelBoxItem.GetRight() ) + { + pNoRightBorder.reset( new SvxBoxItem( aSelBoxItem )); + pNoRightBorder->SetLine( nullptr, SvxBoxItemLine::RIGHT ); + } + for( sal_uInt16 j = 0; j < nCnt; ++j ) + { + SwTableBox *pCurrBox = pLine->GetTabBoxes()[nInsPos+j]; + if( bNewSpan ) + { + pCurrBox->setRowSpan( nLastRowSpan ); + SwFrameFormat* pFrameFormat = pCurrBox->ClaimFrameFormat(); + SwFormatFrameSize aFrameSz( pFrameFormat->GetFrameSize() ); + aFrameSz.SetWidth( nNewBoxWidth ); + pFrameFormat->SetFormatAttr( aFrameSz ); + if( pNoRightBorder && ( !bBehind || j+1 < nCnt ) ) + pFrameFormat->SetFormatAttr( *pNoRightBorder ); + aInsFormat[j] = static_cast<SwTableBoxFormat*>(pFrameFormat); + } + else + pCurrBox->ChgFrameFormat( aInsFormat[j] ); + } + if( bBehind && pNoRightBorder ) + { + SwFrameFormat* pFrameFormat = pBox->ClaimFrameFormat(); + pFrameFormat->SetFormatAttr( *pNoRightBorder ); + } + } + + aFndBox.MakeFrames( *this ); +#if OSL_DEBUG_LEVEL > 0 + { + const SwTableBoxes &rTabBoxes = m_aLines[0]->GetTabBoxes(); + long nNewWidth = 0; + for( size_t i = 0; i < rTabBoxes.size(); ++i ) + nNewWidth += rTabBoxes[i]->GetFrameFormat()->GetFrameSize().GetWidth(); + OSL_ENSURE( nNewWidth > 0, "Very small" ); + } +#endif + CHECK_TABLE( *this ) + + return true; +} + +/** SwTable::PrepareMerge(..) some preparation for the coming Merge(..) + +For the old table model, ::GetMergeSel(..) is called only, +for the new table model, PrepareMerge does the main work. +It modifies all cells to merge (width, border, rowspan etc.) and collects +the cells which have to be deleted by Merge(..) afterwards. +If there are superfluous rows, these cells are put into the deletion list as well. + +@param rPam +the selection to merge + +@param rBoxes +should be empty at the beginning, at the end it is filled with boxes to delete. + +@param ppMergeBox +will be set to the master cell box + +@param pUndo +the undo object to record all changes +can be Null, e.g. when called by Redo(..) + +@return + +*/ + +bool SwTable::PrepareMerge( const SwPaM& rPam, SwSelBoxes& rBoxes, + SwSelBoxes& rMerged, SwTableBox** ppMergeBox, SwUndoTableMerge* pUndo ) +{ + if( !m_bNewModel ) + { + ::GetMergeSel( rPam, rBoxes, ppMergeBox, pUndo ); + return rBoxes.size() > 1; + } + CHECK_TABLE( *this ) + // We have to assert a "rectangular" box selection before we start to merge + std::unique_ptr< SwBoxSelection > pSel( CollectBoxSelection( rPam ) ); + if (!pSel || pSel->isEmpty()) + return false; + // Now we should have a rectangle of boxes, + // i.e. contiguous cells in contiguous rows + bool bMerge = false; // will be set if any content is transferred from + // a "not already overlapped" cell into the new master cell. + const SwSelBoxes& rFirstBoxes = pSel->maBoxes[0]; + if (rFirstBoxes.empty()) + return false; + SwTableBox *pMergeBox = rFirstBoxes[0]; // the master cell box + if( !pMergeBox ) + return false; + (*ppMergeBox) = pMergeBox; + // The new master box will get the left and the top border of the top-left + // box of the selection and because the new master cell _is_ the top-left + // box, the left and right border does not need to be changed. + // The right and bottom border instead has to be derived from the right- + // bottom box of the selection. If this is an overlapped cell, + // the appropriate master box. + SwTableBox* pLastBox = nullptr; // the right-bottom (master) cell + SwDoc* pDoc = GetFrameFormat()->GetDoc(); + SwPosition aInsPos( *pMergeBox->GetSttNd()->EndOfSectionNode() ); + SwPaM aChkPam( aInsPos ); + // The number of lines in the selection rectangle: nLineCount + const size_t nLineCount = pSel->maBoxes.size(); + // BTW: nLineCount is the rowspan of the new master cell + long nRowSpan = static_cast<long>(nLineCount); + // We will need the first and last line of the selection + // to check if there any superfluous row after merging + SwTableLine* pFirstLn = nullptr; + SwTableLine* pLastLn = nullptr; + // Iteration over the lines of the selection... + for( size_t nCurrLine = 0; nCurrLine < nLineCount; ++nCurrLine ) + { + // The selected boxes in the current line + const SwSelBoxes& rLineBoxes = pSel->maBoxes[nCurrLine]; + size_t nColCount = rLineBoxes.size(); + // Iteration over the selected cell in the current row + for (size_t nCurrCol = 0; nCurrCol < nColCount; ++nCurrCol) + { + SwTableBox* pBox = rLineBoxes[nCurrCol]; + rMerged.insert( pBox ); + // Only the first selected cell in every row will be alive, + // the other will be deleted => put into rBoxes + if( nCurrCol ) + rBoxes.insert( pBox ); + else + { + if( nCurrLine == 1 ) + pFirstLn = pBox->GetUpper(); // we need this line later on + if( nCurrLine + 1 == nLineCount ) + pLastLn = pBox->GetUpper(); // and this one, too. + } + // A box has to be merged if it's not the master box itself, + // but an already overlapped cell must not be merged as well. + bool bDoMerge = pBox != pMergeBox && pBox->getRowSpan() > 0; + // The last box has to be in the last "column" of the selection + // and it has to be a master cell + if( nCurrCol+1 == nColCount && pBox->getRowSpan() > 0 ) + pLastBox = pBox; + if( bDoMerge ) + { + bMerge = true; + // If the cell to merge contains only one empty paragraph, + // we do not transfer this paragraph. + if( !IsEmptyBox( *pBox, aChkPam ) ) + { + SwNodeIndex& rInsPosNd = aInsPos.nNode; + SwPaM aPam( aInsPos ); + aPam.GetPoint()->nNode.Assign( *pBox->GetSttNd()->EndOfSectionNode(), -1 ); + SwContentNode* pCNd = aPam.GetContentNode(); + aPam.GetPoint()->nContent.Assign( pCNd, pCNd ? pCNd->Len() : 0 ); + SwNodeIndex aSttNdIdx( *pBox->GetSttNd(), 1 ); + bool const bUndo = pDoc->GetIDocumentUndoRedo().DoesUndo(); + if( pUndo ) + { + pDoc->GetIDocumentUndoRedo().DoUndo(false); + } + pDoc->getIDocumentContentOperations().AppendTextNode( *aPam.GetPoint() ); + if( pUndo ) + { + pDoc->GetIDocumentUndoRedo().DoUndo(bUndo); + } + SwNodeRange aRg( aSttNdIdx, aPam.GetPoint()->nNode ); + if( pUndo ) + pUndo->MoveBoxContent( pDoc, aRg, rInsPosNd ); + else + { + pDoc->getIDocumentContentOperations().MoveNodeRange( aRg, rInsPosNd, + SwMoveFlags::NO_DELFRMS ); + } + } + } + // Only the cell of the first selected column will stay alive + // and got a new row span + if( !nCurrCol ) + pBox->setRowSpan( nRowSpan ); + } + if( nRowSpan > 0 ) // the master cell is done, from now on we set + nRowSpan = -nRowSpan; // negative row spans + ++nRowSpan; // ... -3, -2, -1 + } + if( bMerge ) + { + // A row containing overlapped cells is superfluous, + // these cells can be put into rBoxes for deletion + FindSuperfluousRows_( rBoxes, pFirstLn, pLastLn ); + // pNewFormat will be set to the new master box and the overlapped cells + SwFrameFormat* pNewFormat = pMergeBox->ClaimFrameFormat(); + pNewFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, pSel->mnMergeWidth, 0 ) ); + for( size_t nCurrLine = 0; nCurrLine < nLineCount; ++nCurrLine ) + { + const SwSelBoxes& rLineBoxes = pSel->maBoxes[nCurrLine]; + size_t nColCount = rLineBoxes.size(); + for (size_t nCurrCol = 0; nCurrCol < nColCount; ++nCurrCol) + { + SwTableBox* pBox = rLineBoxes[nCurrCol]; + if( nCurrCol ) + { + // Even this box will be deleted soon, + // we have to correct the width to avoid side effects + SwFrameFormat* pFormat = pBox->ClaimFrameFormat(); + pFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, 0, 0 ) ); + } + else + { + pBox->ChgFrameFormat( static_cast<SwTableBoxFormat*>(pNewFormat) ); + // remove numbering from cells that will be disabled in the merge + if( nCurrLine ) + { + SwPaM aPam( *pBox->GetSttNd(), 0 ); + aPam.GetPoint()->nNode++; + SwTextNode* pNd = aPam.GetNode().GetTextNode(); + while( pNd ) + { + pNd->SetCountedInList( false ); + + aPam.GetPoint()->nNode++; + pNd = aPam.GetNode().GetTextNode(); + } + } + } + } + } + if( pLastBox ) // Robust + { + // The new borders of the master cell... + SvxBoxItem aBox( pMergeBox->GetFrameFormat()->GetBox() ); + bool bOld = aBox.GetRight() || aBox.GetBottom(); + const SvxBoxItem& rBox = pLastBox->GetFrameFormat()->GetBox(); + aBox.SetLine( rBox.GetRight(), SvxBoxItemLine::RIGHT ); + aBox.SetLine( rBox.GetBottom(), SvxBoxItemLine::BOTTOM ); + if( bOld || aBox.GetLeft() || aBox.GetTop() || aBox.GetRight() || aBox.GetBottom() ) + (*ppMergeBox)->GetFrameFormat()->SetFormatAttr( aBox ); + } + + if( pUndo ) + pUndo->AddNewBox( pMergeBox->GetSttIdx() ); + } + return bMerge; +} + +/** SwTable::FindSuperfluousRows_(..) is looking for superfluous rows, i.e. rows + containing overlapped cells only. +*/ + +void SwTable::FindSuperfluousRows_( SwSelBoxes& rBoxes, + SwTableLine* pFirstLn, SwTableLine* pLastLn ) +{ + if( !pFirstLn || !pLastLn ) + { + if( rBoxes.empty() ) + return; + pFirstLn = rBoxes[0]->GetUpper(); + pLastLn = rBoxes.back()->GetUpper(); + } + sal_uInt16 nFirstLn = GetTabLines().GetPos( pFirstLn ); + sal_uInt16 nLastLn = GetTabLines().GetPos( pLastLn ); + for( sal_uInt16 nRow = nFirstLn; nRow <= nLastLn; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + OSL_ENSURE( pLine, "Missing table line" ); + const size_t nCols = pLine->GetTabBoxes().size(); + bool bSuperfl = true; + for( size_t nCol = 0; nCol < nCols; ++nCol ) + { + SwTableBox *pBox = pLine->GetTabBoxes()[nCol]; + if( pBox->getRowSpan() > 0 && + rBoxes.end() == rBoxes.find( pBox ) ) + { + bSuperfl = false; + break; + } + } + if( bSuperfl ) + { + for( size_t nCol = 0; nCol < nCols; ++nCol ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCol]; + rBoxes.insert( pBox ); + } + } + } +} + +/** SwTableBox::FindStartOfRowSpan(..) returns the "master" cell, the cell which + overlaps the given cell, it maybe the cell itself. +*/ + +SwTableBox& SwTableBox::FindStartOfRowSpan( const SwTable& rTable, sal_uInt16 nMaxStep ) +{ + if( getRowSpan() > 0 || !nMaxStep ) + return *this; + + long nLeftBorder = lcl_Box2LeftBorder( *this ); + SwTableBox* pBox = this; + const SwTableLine* pMyUpper = GetUpper(); + sal_uInt16 nLine = rTable.GetTabLines().GetPos( pMyUpper ); + if( nLine && nLine < rTable.GetTabLines().size() ) + { + SwTableBox* pNext; + do + { + pNext = lcl_LeftBorder2Box( nLeftBorder, rTable.GetTabLines()[--nLine] ); + if( pNext ) + pBox = pNext; + } while( nLine && --nMaxStep && pNext && pBox->getRowSpan() < 1 ); + } + + return *pBox; +} + +/** SwTableBox::FindEndOfRowSpan(..) returns the last overlapped cell if there is + any. Otherwise the cell itself will returned. +*/ + +SwTableBox& SwTableBox::FindEndOfRowSpan( const SwTable& rTable, sal_uInt16 nMaxStep ) +{ + long nAbsSpan = getRowSpan(); + if( nAbsSpan < 0 ) + nAbsSpan = -nAbsSpan; + if( nAbsSpan == 1 || !nMaxStep ) + return *this; + + if( nMaxStep > --nAbsSpan ) + nMaxStep = static_cast<sal_uInt16>(nAbsSpan); + const SwTableLine* pMyUpper = GetUpper(); + sal_uInt16 nLine = rTable.GetTabLines().GetPos( pMyUpper ); + nMaxStep = nLine + nMaxStep; + if( nMaxStep >= rTable.GetTabLines().size() ) + nMaxStep = rTable.GetTabLines().size() - 1; + long nLeftBorder = lcl_Box2LeftBorder( *this ); + SwTableBox* pBox = + lcl_LeftBorder2Box( nLeftBorder, rTable.GetTabLines()[ nMaxStep ] ); + if ( !pBox ) + pBox = this; + + return *pBox; +} + +/** lcl_getAllMergedBoxes(..) collects all overlapped boxes to a given (master) box +*/ + +static void lcl_getAllMergedBoxes( const SwTable& rTable, SwSelBoxes& rBoxes, SwTableBox& rBox ) +{ + SwTableBox* pBox = &rBox; + OSL_ENSURE( pBox == &rBox.FindStartOfRowSpan( rTable ), "Not a master box" ); + rBoxes.insert( pBox ); + if( pBox->getRowSpan() == 1 ) + return; + const SwTableLine* pMyUpper = pBox->GetUpper(); + sal_uInt16 nLine = rTable.GetTabLines().GetPos( pMyUpper ); + long nLeftBorder = lcl_Box2LeftBorder( *pBox ); + sal_uInt16 nCount = rTable.GetTabLines().size(); + while( ++nLine < nCount && pBox && pBox->getRowSpan() != -1 ) + { + pBox = lcl_LeftBorder2Box( nLeftBorder, rTable.GetTabLines()[nLine] ); + if( pBox ) + rBoxes.insert( pBox ); + } +} + +/** lcl_UnMerge(..) manipulates the row span attribute of a given master cell + and its overlapped cells to split them into several pieces. +*/ + +static void lcl_UnMerge( const SwTable& rTable, SwTableBox& rBox, size_t nCnt, + bool bSameHeight ) +{ + SwSelBoxes aBoxes; + lcl_getAllMergedBoxes( rTable, aBoxes, rBox ); + size_t const nCount = aBoxes.size(); + if( nCount < 2 ) + return; + if( nCnt > nCount ) + nCnt = nCount; + std::unique_ptr<size_t[]> const pSplitIdx(new size_t[nCnt]); + if( bSameHeight ) + { + std::unique_ptr<SwTwips[]> const pHeights(new SwTwips[nCount]); + SwTwips nHeight = 0; + for (size_t i = 0; i < nCount; ++i) + { + SwTableLine* pLine = aBoxes[ i ]->GetUpper(); + SwFrameFormat *pRowFormat = pLine->GetFrameFormat(); + pHeights[ i ] = pRowFormat->GetFrameSize().GetHeight(); + nHeight += pHeights[ i ]; + } + SwTwips nSumH = 0; + size_t nIdx = 0; + for (size_t i = 1; i <= nCnt; ++i) + { + SwTwips nSplit = ( i * nHeight ) / nCnt; + while( nSumH < nSplit && nIdx < nCount ) + nSumH += pHeights[ nIdx++ ]; + pSplitIdx[ i - 1 ] = nIdx; + } + } + else + { + for (size_t i = 1; i <= nCnt; ++i) + { + pSplitIdx[ i - 1 ] = ( i * nCount ) / nCnt; + } + } + size_t nIdx = 0; + for (size_t i = 0; i < nCnt; ++i) + { + size_t nNextIdx = pSplitIdx[ i ]; + aBoxes[ nIdx ]->setRowSpan( nNextIdx - nIdx ); + lcl_InvalidateCellFrame( *aBoxes[ nIdx ] ); + while( ++nIdx < nNextIdx ) + aBoxes[ nIdx ]->setRowSpan( nIdx - nNextIdx ); + } +} + +/** lcl_FillSelBoxes(..) puts all boxes of a given line into the selection structure +*/ + +static void lcl_FillSelBoxes( SwSelBoxes &rBoxes, SwTableLine &rLine ) +{ + const size_t nBoxCount = rLine.GetTabBoxes().size(); + for( size_t i = 0; i < nBoxCount; ++i ) + rBoxes.insert( rLine.GetTabBoxes()[i] ); +} + +/** SwTable::InsertSpannedRow(..) inserts "superfluous" rows, i.e. rows containing + overlapped cells only. This is a preparation for an upcoming split. +*/ + +void SwTable::InsertSpannedRow( SwDoc* pDoc, sal_uInt16 nRowIdx, sal_uInt16 nCnt ) +{ + CHECK_TABLE( *this ) + OSL_ENSURE( nCnt && nRowIdx < GetTabLines().size(), "Wrong call of InsertSpannedRow" ); + SwSelBoxes aBoxes; + SwTableLine& rLine = *GetTabLines()[ nRowIdx ]; + lcl_FillSelBoxes( aBoxes, rLine ); + SwFormatFrameSize aFSz( rLine.GetFrameFormat()->GetFrameSize() ); + if( SwFrameSize::Variable != aFSz.GetHeightSizeType() ) + { + SwFrameFormat* pFrameFormat = rLine.ClaimFrameFormat(); + long nNewHeight = aFSz.GetHeight() / ( nCnt + 1 ); + if( !nNewHeight ) + ++nNewHeight; + aFSz.SetHeight( nNewHeight ); + pFrameFormat->SetFormatAttr( aFSz ); + } + InsertRow_( pDoc, aBoxes, nCnt, true ); + const size_t nBoxCount = rLine.GetTabBoxes().size(); + for( sal_uInt16 n = 0; n < nCnt; ++n ) + { + SwTableLine *pNewLine = GetTabLines()[ nRowIdx + nCnt - n ]; + for( size_t nCurrBox = 0; nCurrBox < nBoxCount; ++nCurrBox ) + { + long nRowSpan = rLine.GetTabBoxes()[nCurrBox]->getRowSpan(); + if( nRowSpan > 0 ) + nRowSpan = - nRowSpan; + pNewLine->GetTabBoxes()[ nCurrBox ]->setRowSpan( nRowSpan - n ); + } + } + lcl_ChangeRowSpan( *this, nCnt, nRowIdx, false ); + CHECK_TABLE( *this ) +} + +typedef std::pair< sal_uInt16, sal_uInt16 > SwLineOffset; +typedef std::vector< SwLineOffset > SwLineOffsetArray; + +/* +* When a couple of table boxes has to be split, +* lcl_SophisticatedFillLineIndices delivers the information where and how many +* rows have to be inserted. +* Input +* rTable: the table to manipulate +* rBoxes: an array of boxes to split +* nCnt: how many parts are wanted +* Output +* rArr: a list of pairs ( line index, number of lines to insert ) +*/ +static void lcl_SophisticatedFillLineIndices( SwLineOffsetArray &rArr, + const SwTable& rTable, const SwSelBoxes& rBoxes, sal_uInt16 nCnt ) +{ + std::list< SwLineOffset > aBoxes; + SwLineOffset aLnOfs( USHRT_MAX, USHRT_MAX ); + for (size_t i = 0; i < rBoxes.size(); ++i) + { // Collect all end line indices and the row spans + const SwTableBox &rBox = rBoxes[ i ]->FindStartOfRowSpan( rTable ); + OSL_ENSURE( rBox.getRowSpan() > 0, "Didn't I say 'StartOfRowSpan' ??" ); + if( nCnt > rBox.getRowSpan() ) + { + const SwTableLine *pLine = rBox.GetUpper(); + const sal_uInt16 nEnd = sal_uInt16( rBox.getRowSpan() + + rTable.GetTabLines().GetPos( pLine ) ); + // The next if statement is a small optimization + if( aLnOfs.first != nEnd || aLnOfs.second != rBox.getRowSpan() ) + { + aLnOfs.first = nEnd; // ok, this is the line behind the box + aLnOfs.second = sal_uInt16( rBox.getRowSpan() ); // the row span + aBoxes.insert( aBoxes.end(), aLnOfs ); + } + } + } + // As I said, I noted the line index _behind_ the last line of the boxes + // in the resulting array the index has to be _on_ the line + // nSum is to evaluate the wished value + sal_uInt16 nSum = 1; + while( !aBoxes.empty() ) + { + // I. step: + // Looking for the "smallest" line end with the smallest row span + std::list< SwLineOffset >::iterator pCurr = aBoxes.begin(); + aLnOfs = *pCurr; // the line end and row span of the first box + while( ++pCurr != aBoxes.end() ) + { + if( aLnOfs.first > pCurr->first ) + { // Found a smaller line end + aLnOfs.first = pCurr->first; + aLnOfs.second = pCurr->second; // row span + } + else if( aLnOfs.first == pCurr->first && + aLnOfs.second < pCurr->second ) + aLnOfs.second = pCurr->second; // Found a smaller row span + } + OSL_ENSURE( aLnOfs.second < nCnt, "Clean-up failed" ); + aLnOfs.second = nCnt - aLnOfs.second; // the number of rows to insert + rArr.emplace_back( aLnOfs.first - nSum, aLnOfs.second ); + // the correction has to be incremented because in the following + // loops the line ends were manipulated + nSum = nSum + aLnOfs.second; + + pCurr = aBoxes.begin(); + while( pCurr != aBoxes.end() ) + { + if( pCurr->first == aLnOfs.first ) + { // These boxes can be removed because the last insertion + // of rows will expand their row span above the needed value + pCurr = aBoxes.erase(pCurr); + } + else + { + bool bBefore = ( pCurr->first - pCurr->second < aLnOfs.first ); + // Manipulation of the end line indices as if the rows are + // already inserted + pCurr->first = pCurr->first + aLnOfs.second; + if( bBefore ) + { // If the insertion is inside the box, + // its row span has to be incremented + pCurr->second = pCurr->second + aLnOfs.second; + if( pCurr->second >= nCnt ) + { // if the row span is bigger than the split factor + // this box is done + pCurr = aBoxes.erase(pCurr); + } + else + ++pCurr; + } + else + ++pCurr; + } + } + } +} + +typedef std::set< SwTwips > SwSplitLines; + +/** lcl_CalculateSplitLineHeights(..) delivers all y-positions where table rows have + to be split to fulfill the requested "split same height" +*/ + +static sal_uInt16 lcl_CalculateSplitLineHeights( SwSplitLines &rCurr, SwSplitLines &rNew, + const SwTable& rTable, const SwSelBoxes& rBoxes, sal_uInt16 nCnt ) +{ + if( nCnt < 2 ) + return 0; + std::vector< SwLineOffset > aBoxes; + SwLineOffset aLnOfs( USHRT_MAX, USHRT_MAX ); + sal_uInt16 nFirst = USHRT_MAX; // becomes the index of the first line + sal_uInt16 nLast = 0; // becomes the index of the last line of the splitting + for (size_t i = 0; i < rBoxes.size(); ++i) + { // Collect all pairs (start+end) of line indices to split + const SwTableBox &rBox = rBoxes[ i ]->FindStartOfRowSpan( rTable ); + OSL_ENSURE( rBox.getRowSpan() > 0, "Didn't I say 'StartOfRowSpan' ??" ); + const SwTableLine *pLine = rBox.GetUpper(); + const sal_uInt16 nStart = rTable.GetTabLines().GetPos( pLine ); + const sal_uInt16 nEnd = sal_uInt16( rBox.getRowSpan() + nStart - 1 ); + // The next if statement is a small optimization + if( aLnOfs.first != nStart || aLnOfs.second != nEnd ) + { + aLnOfs.first = nStart; + aLnOfs.second = nEnd; + aBoxes.push_back( aLnOfs ); + if( nStart < nFirst ) + nFirst = nStart; + if( nEnd > nLast ) + nLast = nEnd; + } + } + + if (nFirst == USHRT_MAX) + { + assert(aBoxes.empty()); + return 0; + } + + SwTwips nHeight = 0; + std::unique_ptr<SwTwips[]> pLines(new SwTwips[ nLast + 1 - nFirst ]); + for( sal_uInt16 i = nFirst; i <= nLast; ++i ) + { + bool bLayoutAvailable = false; + nHeight += rTable.GetTabLines()[ i ]->GetTableLineHeight( bLayoutAvailable ); + rCurr.insert( rCurr.end(), nHeight ); + pLines[ i - nFirst ] = nHeight; + } + for( const auto& rSplit : aBoxes ) + { + SwTwips nBase = rSplit.first <= nFirst ? 0 : + pLines[ rSplit.first - nFirst - 1 ]; + SwTwips nDiff = pLines[ rSplit.second - nFirst ] - nBase; + for( sal_uInt16 i = 1; i < nCnt; ++i ) + { + SwTwips nSplit = nBase + ( i * nDiff ) / nCnt; + rNew.insert( nSplit ); + } + } + return nFirst; +} + +/** lcl_LineIndex(..) delivers the line index of the line behind or above + the box selection. +*/ + +static sal_uInt16 lcl_LineIndex( const SwTable& rTable, const SwSelBoxes& rBoxes, + bool bBehind ) +{ + sal_uInt16 nDirect = USHRT_MAX; + sal_uInt16 nSpan = USHRT_MAX; + for (size_t i = 0; i < rBoxes.size(); ++i) + { + SwTableBox *pBox = rBoxes[i]; + const SwTableLine* pLine = rBoxes[i]->GetUpper(); + sal_uInt16 nPos = rTable.GetTabLines().GetPos( pLine ); + if( USHRT_MAX != nPos ) + { + if( bBehind ) + { + if( nPos > nDirect || nDirect == USHRT_MAX ) + nDirect = nPos; + long nRowSpan = pBox->getRowSpan(); + if( nRowSpan < 2 ) + nSpan = 0; + else if( nSpan ) + { + sal_uInt16 nEndOfRowSpan = static_cast<sal_uInt16>(nPos + nRowSpan - 1); + if( nEndOfRowSpan > nSpan || nSpan == USHRT_MAX ) + nSpan = nEndOfRowSpan; + } + } + else if( nPos < nDirect ) + nDirect = nPos; + } + } + if( nSpan && nSpan < USHRT_MAX ) + return nSpan; + return nDirect; +} + +/** SwTable::NewSplitRow(..) splits all selected boxes horizontally. +*/ + +bool SwTable::NewSplitRow( SwDoc* pDoc, const SwSelBoxes& rBoxes, sal_uInt16 nCnt, + bool bSameHeight ) +{ + CHECK_TABLE( *this ) + ++nCnt; + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( rBoxes, *this ); + + if( bSameHeight && pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + SwSplitLines aRowLines; + SwSplitLines aSplitLines; + sal_uInt16 nFirst = lcl_CalculateSplitLineHeights( aRowLines, aSplitLines, + *this, rBoxes, nCnt ); + aFndBox.DelFrames( *this ); + SwTwips nLast = 0; + SwSplitLines::iterator pSplit = aSplitLines.begin(); + for( const auto& rCurr : aRowLines ) + { + while( pSplit != aSplitLines.end() && *pSplit < rCurr ) + { + InsertSpannedRow( pDoc, nFirst, 1 ); + SwTableLine* pRow = GetTabLines()[ nFirst ]; + SwFrameFormat* pRowFormat = pRow->ClaimFrameFormat(); + SwFormatFrameSize aFSz( pRowFormat->GetFrameSize() ); + aFSz.SetHeightSizeType( SwFrameSize::Minimum ); + aFSz.SetHeight( *pSplit - nLast ); + pRowFormat->SetFormatAttr( aFSz ); + nLast = *pSplit; + ++pSplit; + ++nFirst; + } + if( pSplit != aSplitLines.end() && rCurr == *pSplit ) + ++pSplit; + SwTableLine* pRow = GetTabLines()[ nFirst ]; + SwFrameFormat* pRowFormat = pRow->ClaimFrameFormat(); + SwFormatFrameSize aFSz( pRowFormat->GetFrameSize() ); + aFSz.SetHeightSizeType( SwFrameSize::Minimum ); + aFSz.SetHeight( rCurr - nLast ); + pRowFormat->SetFormatAttr( aFSz ); + nLast = rCurr; + ++nFirst; + } + } + else + { + aFndBox.DelFrames( *this ); + bSameHeight = false; + } + if( !bSameHeight ) + { + SwLineOffsetArray aLineOffs; + lcl_SophisticatedFillLineIndices( aLineOffs, *this, rBoxes, nCnt ); + SwLineOffsetArray::reverse_iterator pCurr( aLineOffs.rbegin() ); + while( pCurr != aLineOffs.rend() ) + { + InsertSpannedRow( pDoc, pCurr->first, pCurr->second ); + ++pCurr; + } + } + + std::set<size_t> aIndices; + for (size_t i = 0; i < rBoxes.size(); ++i) + { + OSL_ENSURE( rBoxes[i]->getRowSpan() != 1, "Forgot to split?" ); + if( rBoxes[i]->getRowSpan() > 1 ) + aIndices.insert( i ); + } + + for( const auto& rCurrBox : aIndices ) + lcl_UnMerge( *this, *rBoxes[rCurrBox], nCnt, bSameHeight ); + + CHECK_TABLE( *this ) + // update the layout + aFndBox.MakeFrames( *this ); + + return true; +} + +/** SwTable::InsertRow(..) inserts one or more rows before or behind the selected + boxes. +*/ + +bool SwTable::InsertRow( SwDoc* pDoc, const SwSelBoxes& rBoxes, + sal_uInt16 nCnt, bool bBehind ) +{ + bool bRet = false; + if( IsNewModel() ) + { + CHECK_TABLE( *this ) + sal_uInt16 nRowIdx = lcl_LineIndex( *this, rBoxes, bBehind ); + if( nRowIdx < USHRT_MAX ) + { + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( rBoxes, *this ); + aFndBox.DelFrames( *this ); + + bRet = true; + SwTableLine *pLine = GetTabLines()[ nRowIdx ]; + SwSelBoxes aLineBoxes; + lcl_FillSelBoxes( aLineBoxes, *pLine ); + InsertRow_( pDoc, aLineBoxes, nCnt, bBehind ); + const size_t nBoxCount = pLine->GetTabBoxes().size(); + sal_uInt16 nOfs = bBehind ? 0 : 1; + for( sal_uInt16 n = 0; n < nCnt; ++n ) + { + SwTableLine *pNewLine = GetTabLines()[ nRowIdx+nCnt-n-nOfs]; + for( size_t nCurrBox = 0; nCurrBox < nBoxCount; ++nCurrBox ) + { + long nRowSpan = pLine->GetTabBoxes()[nCurrBox]->getRowSpan(); + if( bBehind ) + { + if( nRowSpan == 1 || nRowSpan == -1 ) + nRowSpan = n + 1; + else if( nRowSpan > 1 ) + { + nRowSpan = - nRowSpan; + + // tdf#123102 disable numbering of the new hidden + // paragraph in merged cells to avoid of bad + // renumbering of next list elements + SwTableBox* pBox = pNewLine->GetTabBoxes()[nCurrBox]; + SwNodeIndex aIdx( *pBox->GetSttNd(), +1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( pCNd && pCNd->IsTextNode() && pCNd->GetTextNode()->GetNumRule() ) + { + SwPosition aPos( *pCNd->GetTextNode() ); + SwPaM aPam( aPos, aPos ); + pDoc->DelNumRules( aPam ); + } + } + } + else + { + if( nRowSpan > 0 ) + nRowSpan = n + 1; + else + --nRowSpan; + } + pNewLine->GetTabBoxes()[ nCurrBox ]->setRowSpan( nRowSpan - n ); + } + } + if( bBehind ) + ++nRowIdx; + if( nRowIdx ) + lcl_ChangeRowSpan( *this, nCnt, --nRowIdx, true ); + // update the layout + aFndBox.MakeFrames( *this ); + } + CHECK_TABLE( *this ) + } + else + bRet = InsertRow_( pDoc, rBoxes, nCnt, bBehind ); + return bRet; +} + +/** SwTable::PrepareDelBoxes(..) adjusts the row span attributes for an upcoming + deletion of table cells and invalidates the layout of these cells. +*/ + +void SwTable::PrepareDelBoxes( const SwSelBoxes& rBoxes ) +{ + if( IsNewModel() ) + { + for (size_t i = 0; i < rBoxes.size(); ++i) + { + SwTableBox* pBox = rBoxes[i]; + long nRowSpan = pBox->getRowSpan(); + if( nRowSpan != 1 && pBox->GetFrameFormat()->GetFrameSize().GetWidth() ) + { + long nLeft = lcl_Box2LeftBorder( *pBox ); + SwTableLine *pLine = pBox->GetUpper(); + sal_uInt16 nLinePos = GetTabLines().GetPos( pLine); + OSL_ENSURE( nLinePos < USHRT_MAX, "Box/table mismatch" ); + if( nRowSpan > 1 ) + { + if( ++nLinePos < GetTabLines().size() ) + { + pLine = GetTabLines()[ nLinePos ]; + pBox = lcl_LeftBorder2Box( nLeft, pLine ); + OSL_ENSURE( pBox, "RowSpan irritation I" ); + if( pBox ) + pBox->setRowSpan( --nRowSpan ); + } + } + else if( nLinePos > 0 ) + { + do + { + pLine = GetTabLines()[ --nLinePos ]; + pBox = lcl_LeftBorder2Box( nLeft, pLine ); + OSL_ENSURE( pBox, "RowSpan irritation II" ); + if( pBox ) + { + nRowSpan = pBox->getRowSpan(); + if( nRowSpan > 1 ) + { + lcl_InvalidateCellFrame( *pBox ); + --nRowSpan; + } + else + ++nRowSpan; + pBox->setRowSpan( nRowSpan ); + } + else + nRowSpan = 1; + } + while( nRowSpan < 0 && nLinePos > 0 ); + } + } + } + } +} + +/** lcl_SearchSelBox(..) adds cells of a given table row to the selection structure + if it overlaps with the given x-position range +*/ + +static void lcl_SearchSelBox( const SwTable &rTable, SwSelBoxes& rBoxes, long nMin, long nMax, + SwTableLine& rLine, bool bChkProtected, bool bColumn ) +{ + long nLeft = 0; + long nRight = 0; + long nMid = ( nMax + nMin )/ 2; + const size_t nCount = rLine.GetTabBoxes().size(); + for( size_t nCurrBox = 0; nCurrBox < nCount; ++nCurrBox ) + { + SwTableBox* pBox = rLine.GetTabBoxes()[nCurrBox]; + OSL_ENSURE( pBox, "Missing table box" ); + long nWidth = pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + nRight += nWidth; + if( nRight > nMin ) + { + bool bAdd = false; + if( nRight <= nMax ) + bAdd = nLeft >= nMin || nRight >= nMid || + nRight - nMin > nMin - nLeft; + else + bAdd = nLeft <= nMid || nRight - nMax < nMax - nLeft; + long nRowSpan = pBox->getRowSpan(); + if( bAdd && + ( !bChkProtected || + !pBox->GetFrameFormat()->GetProtect().IsContentProtected() ) ) + { + size_t const nOldCnt = rBoxes.size(); + rBoxes.insert( pBox ); + if( bColumn && nRowSpan != 1 && nOldCnt < rBoxes.size() ) + { + SwTableBox *pMasterBox = pBox->getRowSpan() > 0 ? pBox + : &pBox->FindStartOfRowSpan( rTable ); + lcl_getAllMergedBoxes( rTable, rBoxes, *pMasterBox ); + } + } + } + if( nRight >= nMax ) + break; + nLeft = nRight; + } +} + +/** void SwTable::CreateSelection(..) fills the selection structure with table cells + for a given SwPaM, ie. start and end position inside a table +*/ + +void SwTable::CreateSelection( const SwPaM& rPam, SwSelBoxes& rBoxes, + const SearchType eSearch, bool bChkProtected ) const +{ + OSL_ENSURE( m_bNewModel, "Don't call me for old tables" ); + if( m_aLines.empty() ) + return; + const SwNode* pStartNd = rPam.GetPoint()->nNode.GetNode().FindTableBoxStartNode(); + const SwNode* pEndNd = rPam.GetMark()->nNode.GetNode().FindTableBoxStartNode(); + if( !pStartNd || !pEndNd ) + return; + CreateSelection( pStartNd, pEndNd, rBoxes, eSearch, bChkProtected ); +} + +/** void SwTable::CreateSelection(..) fills the selection structure with table cells + for given start and end nodes inside a table +*/ +void SwTable::CreateSelection( const SwNode* pStartNd, const SwNode* pEndNd, + SwSelBoxes& rBoxes, const SearchType eSearch, bool bChkProtected ) const +{ + rBoxes.clear(); + // Looking for start and end of the selection given by SwNode-pointer + const size_t nLines = m_aLines.size(); + // nTop becomes the line number of the upper box + // nBottom becomes the line number of the lower box + size_t nTop = 0; + size_t nBottom = 0; + // nUpperMin becomes the left border value of the upper box + // nUpperMax becomes the right border of the upper box + // nLowerMin and nLowerMax the borders of the lower box + long nUpperMin = 0, nUpperMax = 0; + long nLowerMin = 0, nLowerMax = 0; + // nFound will incremented if a box is found + // 0 => no box found; 1 => the upper box has been found; 2 => both found + int nFound = 0; + for( size_t nRow = 0; nFound < 2 && nRow < nLines; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + OSL_ENSURE( pLine, "Missing table line" ); + const size_t nCols = pLine->GetTabBoxes().size(); + for( size_t nCol = 0; nCol < nCols; ++nCol ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCol]; + OSL_ENSURE( pBox, "Missing table box" ); + if( pBox->GetSttNd() == pEndNd || pBox->GetSttNd() == pStartNd ) + { + if( !bChkProtected || + !pBox->GetFrameFormat()->GetProtect().IsContentProtected() ) + rBoxes.insert( pBox ); + if( nFound ) + { + nBottom = nRow; + lcl_CheckMinMax( nLowerMin, nLowerMax, *pLine, nCol, true ); + ++nFound; + break; + } + else + { + nTop = nRow; + lcl_CheckMinMax( nUpperMin, nUpperMax, *pLine, nCol, true ); + ++nFound; + // If start and end node are identical, we're nearly done... + if( pEndNd == pStartNd ) + { + nBottom = nTop; + nLowerMin = nUpperMin; + nLowerMax = nUpperMax; + ++nFound; + } + } + } + } + } + if( nFound < 2 ) + return; // At least one node was not a part of the given table + if( eSearch == SEARCH_ROW ) + { + // Selection of a row is quiet easy: + // every (unprotected) box between start and end line + // with a positive row span will be collected + for( size_t nRow = nTop; nRow <= nBottom; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + OSL_ENSURE( pLine, "Missing table line" ); + const size_t nCount = pLine->GetTabBoxes().size(); + for( size_t nCurrBox = 0; nCurrBox < nCount; ++nCurrBox ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrBox]; + OSL_ENSURE( pBox, "Missing table box" ); + if( pBox->getRowSpan() > 0 && ( !bChkProtected || + !pBox->GetFrameFormat()->GetProtect().IsContentProtected() ) ) + rBoxes.insert( pBox ); + } + } + return; + } + bool bCombine = nTop == nBottom; + if( !bCombine ) + { + long nMinWidth = nUpperMax - nUpperMin; + long nTmp = nLowerMax - nLowerMin; + if( nMinWidth > nTmp ) + nMinWidth = nTmp; + nTmp = std::min(nLowerMax, nUpperMax); + nTmp -= ( nLowerMin < nUpperMin ) ? nUpperMin : nLowerMin; + // If the overlapping between upper and lower box is less than half + // of the width (of the smaller cell), bCombine is set, + // e.g. if upper and lower cell are in different columns + bCombine = ( nTmp + nTmp < nMinWidth ); + } + if( bCombine ) + { + if( nUpperMin < nLowerMin ) + nLowerMin = nUpperMin; + else + nUpperMin = nLowerMin; + if( nUpperMax > nLowerMax ) + nLowerMax = nUpperMax; + else + nUpperMax = nLowerMax; + } + const bool bColumn = eSearch == SEARCH_COL; + if( bColumn ) + { + for( size_t i = 0; i < nTop; ++i ) + lcl_SearchSelBox( *this, rBoxes, nUpperMin, nUpperMax, + *m_aLines[i], bChkProtected, bColumn ); + } + + { + long nMin = std::min(nUpperMin, nLowerMin); + long nMax = nUpperMax < nLowerMax ? nLowerMax : nUpperMax; + for( size_t i = nTop; i <= nBottom; ++i ) + lcl_SearchSelBox( *this, rBoxes, nMin, nMax, *m_aLines[i], + bChkProtected, bColumn ); + } + if( bColumn ) + { + for( size_t i = nBottom + 1; i < nLines; ++i ) + lcl_SearchSelBox( *this, rBoxes, nLowerMin, nLowerMax, *m_aLines[i], + bChkProtected, true ); + } +} + +/** void SwTable::ExpandColumnSelection(..) adds cell to the give selection to + assure that at least one cell of every row is part of the selection. +*/ + +void SwTable::ExpandColumnSelection( SwSelBoxes& rBoxes, long &rMin, long &rMax ) const +{ + OSL_ENSURE( m_bNewModel, "Don't call me for old tables" ); + rMin = 0; + rMax = 0; + if( m_aLines.empty() || rBoxes.empty() ) + return; + + const size_t nLineCnt = m_aLines.size(); + const size_t nBoxCnt = rBoxes.size(); + size_t nBox = 0; + for( size_t nRow = 0; nRow < nLineCnt && nBox < nBoxCnt; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + OSL_ENSURE( pLine, "Missing table line" ); + const size_t nCols = pLine->GetTabBoxes().size(); + for( size_t nCol = 0; nCol < nCols; ++nCol ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCol]; + OSL_ENSURE( pBox, "Missing table box" ); + if( pBox == rBoxes[nBox] ) + { + lcl_CheckMinMax( rMin, rMax, *pLine, nCol, nBox == 0 ); + if( ++nBox >= nBoxCnt ) + break; + } + } + } + for( size_t nRow = 0; nRow < nLineCnt; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + const size_t nCols = pLine->GetTabBoxes().size(); + long nRight = 0; + for( size_t nCurrBox = 0; nCurrBox < nCols; ++nCurrBox ) + { + long nLeft = nRight; + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrBox]; + nRight += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + if( nLeft >= rMin && nRight <= rMax ) + rBoxes.insert( pBox ); + } + } +} + +/** SwTable::PrepareDeleteCol(..) adjusts the widths of the neighbour cells of + a cell selection for an upcoming (column) deletion +*/ +void SwTable::PrepareDeleteCol( long nMin, long nMax ) +{ + OSL_ENSURE( m_bNewModel, "Don't call me for old tables" ); + if( m_aLines.empty() || nMax < nMin ) + return; + long nMid = nMin ? ( nMin + nMax ) / 2 : 0; + const SwTwips nTabSize = GetFrameFormat()->GetFrameSize().GetWidth(); + if( nTabSize == nMax ) + nMid = nMax; + const size_t nLineCnt = m_aLines.size(); + for( size_t nRow = 0; nRow < nLineCnt; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + const size_t nCols = pLine->GetTabBoxes().size(); + long nRight = 0; + for( size_t nCurrBox = 0; nCurrBox < nCols; ++nCurrBox ) + { + long nLeft = nRight; + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrBox]; + nRight += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + if( nRight < nMin ) + continue; + if( nLeft > nMax ) + break; + long nNewWidth = -1; + if( nLeft < nMin ) + { + if( nRight <= nMax ) + nNewWidth = nMid - nLeft; + } + else if( nRight > nMax ) + nNewWidth = nRight - nMid; + else + nNewWidth = 0; + if( nNewWidth >= 0 ) + { + SwFrameFormat* pFrameFormat = pBox->ClaimFrameFormat(); + SwFormatFrameSize aFrameSz( pFrameFormat->GetFrameSize() ); + aFrameSz.SetWidth( nNewWidth ); + pFrameFormat->SetFormatAttr( aFrameSz ); + } + } + } +} + +/** SwTable::ExpandSelection(..) adds all boxes to the box selections which are + overlapped by it. +*/ + +void SwTable::ExpandSelection( SwSelBoxes& rBoxes ) const +{ + for (size_t i = 0; i < rBoxes.size(); ++i) + { + SwTableBox *pBox = rBoxes[i]; + long nRowSpan = pBox->getRowSpan(); + if( nRowSpan != 1 ) + { + SwTableBox *pMasterBox = nRowSpan > 0 ? pBox + : &pBox->FindStartOfRowSpan( *this ); + lcl_getAllMergedBoxes( *this, rBoxes, *pMasterBox ); + } + } +} + +/** SwTable::CheckRowSpan(..) looks for the next line without an overlapping to + the previous line. +*/ + +void SwTable::CheckRowSpan( SwTableLine* &rpLine, bool bUp ) const +{ + OSL_ENSURE( IsNewModel(), "Don't call me for old tables" ); + sal_uInt16 nLineIdx = GetTabLines().GetPos( rpLine ); + OSL_ENSURE( nLineIdx < GetTabLines().size(), "Start line out of range" ); + bool bChange = true; + if( bUp ) + { + while( bChange ) + { + bChange = false; + rpLine = GetTabLines()[ nLineIdx ]; + const size_t nCols = rpLine->GetTabBoxes().size(); + for( size_t nCol = 0; !bChange && nCol < nCols; ++nCol ) + { + SwTableBox* pBox = rpLine->GetTabBoxes()[nCol]; + if( pBox->getRowSpan() > 1 || pBox->getRowSpan() < -1 ) + bChange = true; + } + if( bChange ) + { + if( nLineIdx ) + --nLineIdx; + else + { + bChange = false; + rpLine = nullptr; + } + } + } + } + else + { + const size_t nMaxLine = GetTabLines().size(); + while( bChange ) + { + bChange = false; + rpLine = GetTabLines()[ nLineIdx ]; + const size_t nCols = rpLine->GetTabBoxes().size(); + for( size_t nCol = 0; !bChange && nCol < nCols; ++nCol ) + { + SwTableBox* pBox = rpLine->GetTabBoxes()[nCol]; + if( pBox->getRowSpan() < 0 ) + bChange = true; + } + if( bChange ) + { + ++nLineIdx; + if( nLineIdx >= nMaxLine ) + { + bChange = false; + rpLine = nullptr; + } + } + } + } +} + +// This structure corrects the row span attributes for a top line of a table +// In a top line no negative row span is allowed, so these have to be corrected. +// If there has been at least one correction, all values are stored +// and can be used by undo of table split +SwSaveRowSpan::SwSaveRowSpan( SwTableBoxes& rBoxes, sal_uInt16 nSplitLn ) + : mnSplitLine( nSplitLn ) +{ + bool bDontSave = true; // nothing changed, nothing to save + const size_t nColCount = rBoxes.size(); + OSL_ENSURE( nColCount, "Empty Table Line" ); + mnRowSpans.resize( nColCount ); + for( size_t nCurrCol = 0; nCurrCol < nColCount; ++nCurrCol ) + { + SwTableBox* pBox = rBoxes[nCurrCol]; + OSL_ENSURE( pBox, "Missing Table Box" ); + long nRowSp = pBox->getRowSpan(); + mnRowSpans[ nCurrCol ] = nRowSp; + if( nRowSp < 0 ) + { + bDontSave = false; + nRowSp = -nRowSp; + pBox->setRowSpan( nRowSp ); // correction needed + } + } + if( bDontSave ) + mnRowSpans.clear(); +} + +// This function is called by undo of table split to restore the old row span +// values at the split line +void SwTable::RestoreRowSpan( const SwSaveRowSpan& rSave ) +{ + if( !IsNewModel() ) // for new model only + return; + sal_uInt16 nLineCount = GetTabLines().size(); + OSL_ENSURE( rSave.mnSplitLine < nLineCount, "Restore behind last line?" ); + if( rSave.mnSplitLine < nLineCount ) + { + SwTableLine* pLine = GetTabLines()[rSave.mnSplitLine]; + const size_t nColCount = pLine->GetTabBoxes().size(); + OSL_ENSURE( nColCount, "Empty Table Line" ); + OSL_ENSURE( nColCount == rSave.mnRowSpans.size(), "Wrong row span store" ); + if( nColCount == rSave.mnRowSpans.size() ) + { + for( size_t nCurrCol = 0; nCurrCol < nColCount; ++nCurrCol ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrCol]; + OSL_ENSURE( pBox, "Missing Table Box" ); + long nRowSp = pBox->getRowSpan(); + if( nRowSp != rSave.mnRowSpans[ nCurrCol ] ) + { + OSL_ENSURE( -nRowSp == rSave.mnRowSpans[ nCurrCol ], "Pardon me?!" ); + OSL_ENSURE( rSave.mnRowSpans[ nCurrCol ] < 0, "Pardon me?!" ); + pBox->setRowSpan( -nRowSp ); + + sal_uInt16 nLine = rSave.mnSplitLine; + if( nLine ) + { + long nLeftBorder = lcl_Box2LeftBorder( *pBox ); + SwTableBox* pNext; + do + { + pNext = lcl_LeftBorder2Box( nLeftBorder, GetTabLines()[--nLine] ); + if( pNext ) + { + pBox = pNext; + long nNewSpan = pBox->getRowSpan(); + if( pBox->getRowSpan() < 1 ) + nNewSpan -= nRowSp; + else + { + nNewSpan += nRowSp; + pNext = nullptr; + } + pBox->setRowSpan( nNewSpan ); + } + } while( nLine && pNext ); + } + } + } + } + } +} + +std::unique_ptr<SwSaveRowSpan> SwTable::CleanUpTopRowSpan( sal_uInt16 nSplitLine ) +{ + if( !IsNewModel() ) + return nullptr; + std::unique_ptr<SwSaveRowSpan> pRet(new SwSaveRowSpan( GetTabLines()[0]->GetTabBoxes(), nSplitLine )); + if( pRet->mnRowSpans.empty() ) + return nullptr; + return pRet; +} + +void SwTable::CleanUpBottomRowSpan( sal_uInt16 nDelLines ) +{ + if( !IsNewModel() ) + return; + const size_t nLastLine = GetTabLines().size()-1; + SwTableLine* pLine = GetTabLines()[nLastLine]; + const size_t nColCount = pLine->GetTabBoxes().size(); + OSL_ENSURE( nColCount, "Empty Table Line" ); + for( size_t nCurrCol = 0; nCurrCol < nColCount; ++nCurrCol ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrCol]; + OSL_ENSURE( pBox, "Missing Table Box" ); + long nRowSp = pBox->getRowSpan(); + if( nRowSp < 0 ) + nRowSp = -nRowSp; + if( nRowSp > 1 ) + { + lcl_ChangeRowSpan( *this, -static_cast<long>(nDelLines), + static_cast<sal_uInt16>(nLastLine), false ); + break; + } + } +} + +#ifdef DBG_UTIL + +namespace { + +struct RowSpanCheck +{ + long nRowSpan; + SwTwips nLeft; + SwTwips nRight; +}; + +} + +void SwTable::CheckConsistency() const +{ + if( !IsNewModel() ) + return; + const size_t nLineCount = GetTabLines().size(); + const SwTwips nTabSize = GetFrameFormat()->GetFrameSize().GetWidth(); + SwTwips nLineWidth = 0; + std::list< RowSpanCheck > aRowSpanCells; + std::list< RowSpanCheck >::iterator aIter = aRowSpanCells.end(); + SwNodeIndex index(*GetTableNode()); + ++index; + for( size_t nCurrLine = 0; nCurrLine < nLineCount; ++nCurrLine ) + { + SwTwips nWidth = 0; + SwTableLine* pLine = GetTabLines()[nCurrLine]; + SAL_WARN_IF( !pLine, "sw.core", "Missing Table Line" ); + const size_t nColCount = pLine->GetTabBoxes().size(); + SAL_WARN_IF( !nColCount, "sw.core", "Empty Table Line" ); + for( size_t nCurrCol = 0; nCurrCol < nColCount; ++nCurrCol ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrCol]; + assert(pBox); + SAL_WARN_IF(GetTableNode()->EndOfSectionIndex() <= index.GetIndex(), "sw.core", "Box not in table nodes"); + SAL_WARN_IF(!index.GetNode().IsStartNode(), "sw.core", "No box start node"); + index = *index.GetNode().EndOfSectionNode(); + ++index; + SwTwips nNewWidth = pBox->GetFrameFormat()->GetFrameSize().GetWidth() + nWidth; + long nRowSp = pBox->getRowSpan(); + if( nRowSp < 0 ) + { + SAL_WARN_IF( aIter == aRowSpanCells.end(), + "sw.core", "Missing master box"); + if (aIter != aRowSpanCells.end()) + { + SAL_WARN_IF( aIter->nLeft != nWidth || aIter->nRight != nNewWidth, + "sw.core", "Wrong position/size of overlapped table box"); + --(aIter->nRowSpan); + SAL_WARN_IF( aIter->nRowSpan != -nRowSp, "sw.core", + "Wrong row span value" ); + if( nRowSp == -1 ) + { + aIter = aRowSpanCells.erase(aIter); + } + else + ++aIter; + } + } + else if( nRowSp != 1 ) + { + SAL_WARN_IF( !nRowSp, "sw.core", "Zero row span?!" ); + RowSpanCheck aEntry; + aEntry.nLeft = nWidth; + aEntry.nRight = nNewWidth; + aEntry.nRowSpan = nRowSp; + aRowSpanCells.insert( aIter, aEntry ); + } + nWidth = nNewWidth; + } + if( !nCurrLine ) + nLineWidth = nWidth; + SAL_WARN_IF( nWidth != nLineWidth, "sw.core", + "Different Line Widths: first: " << nLineWidth + << " current [" << nCurrLine << "]: " << nWidth); + SAL_WARN_IF( std::abs(nWidth - nTabSize) > 1 /* how tolerant? */, "sw.core", + "Line width differs from table width: " << nTabSize + << " current [" << nCurrLine << "]: " << nWidth); + SAL_WARN_IF( nWidth < 0 || nWidth > USHRT_MAX, "sw.core", + "Width out of range [" << nCurrLine << "]: " << nWidth); + SAL_WARN_IF( aIter != aRowSpanCells.end(), "sw.core", + "Missing overlapped box" ); + aIter = aRowSpanCells.begin(); + } + bool bEmpty = aRowSpanCells.empty(); + SAL_WARN_IF( !bEmpty, "sw.core", "Open row span detected" ); + SAL_WARN_IF(GetTableNode()->EndOfSectionNode() != &index.GetNode(), "sw.core", "table end node not found"); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/table/swtable.cxx b/sw/source/core/table/swtable.cxx new file mode 100644 index 000000000..e3e083127 --- /dev/null +++ b/sw/source/core/table/swtable.cxx @@ -0,0 +1,2771 @@ +/* -*- 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 <float.h> +#include <hintids.hxx> +#include <hints.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/colritem.hxx> +#include <sfx2/linkmgr.hxx> +#include <fmtfsize.hxx> +#include <fmtornt.hxx> +#include <fmtpdsc.hxx> +#include <fldbas.hxx> +#include <fmtfld.hxx> +#include <frmatr.hxx> +#include <doc.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <docary.hxx> +#include <frame.hxx> +#include <swtable.hxx> +#include <ndtxt.hxx> +#include <tabcol.hxx> +#include <tabfrm.hxx> +#include <cellfrm.hxx> +#include <rowfrm.hxx> +#include <swserv.hxx> +#include <expfld.hxx> +#include <mdiexp.hxx> +#include <cellatr.hxx> +#include <txatbase.hxx> +#include <htmltbl.hxx> +#include <swtblfmt.hxx> +#include <ndindex.hxx> +#include <tblrwcl.hxx> +#include <shellres.hxx> +#include <viewsh.hxx> +#include <redline.hxx> +#include <list> +#include <calbck.hxx> + +#ifdef DBG_UTIL +#define CHECK_TABLE(t) (t).CheckConsistency(); +#else +#define CHECK_TABLE(t) +#endif + +using namespace com::sun::star; + + +#define COLFUZZY 20 + +static void ChgTextToNum( SwTableBox& rBox, const OUString& rText, const Color* pCol, + bool bChgAlign, sal_uLong nNdPos ); + +inline const Color* SwTableBox::GetSaveUserColor() const +{ + return mpUserColor.get(); +} + +inline const Color* SwTableBox::GetSaveNumFormatColor() const +{ + return mpNumFormatColor.get(); +} + +inline void SwTableBox::SetSaveUserColor(const Color* p ) +{ + if (p) + mpUserColor.reset(new Color(*p)); + else + mpUserColor.reset(); +} + +inline void SwTableBox::SetSaveNumFormatColor( const Color* p ) +{ + if (p) + mpNumFormatColor.reset(new Color(*p)); + else + mpNumFormatColor.reset(); +} + +long SwTableBox::getRowSpan() const +{ + return mnRowSpan; +} + +void SwTableBox::setRowSpan( long nNewRowSpan ) +{ + mnRowSpan = nNewRowSpan; +} + +bool SwTableBox::getDummyFlag() const +{ + return mbDummyFlag; +} + +void SwTableBox::setDummyFlag( bool bDummy ) +{ + mbDummyFlag = bDummy; +} + +//JP 15.09.98: Bug 55741 - Keep tabs (front and rear) +static OUString& lcl_TabToBlankAtSttEnd( OUString& rText ) +{ + sal_Unicode c; + sal_Int32 n; + + for( n = 0; n < rText.getLength() && ' ' >= ( c = rText[n] ); ++n ) + if( '\x9' == c ) + rText = rText.replaceAt( n, 1, " " ); + for( n = rText.getLength(); n && ' ' >= ( c = rText[--n] ); ) + if( '\x9' == c ) + rText = rText.replaceAt( n, 1, " " ); + return rText; +} + +static OUString& lcl_DelTabsAtSttEnd( OUString& rText ) +{ + sal_Unicode c; + sal_Int32 n; + OUStringBuffer sBuff(rText); + + for( n = 0; n < sBuff.getLength() && ' ' >= ( c = sBuff[ n ]); ++n ) + { + if( '\x9' == c ) + sBuff.remove( n--, 1 ); + } + for( n = sBuff.getLength(); n && ' ' >= ( c = sBuff[ --n ]); ) + { + if( '\x9' == c ) + sBuff.remove( n, 1 ); + } + rText = sBuff.makeStringAndClear(); + return rText; +} + +void InsTableBox( SwDoc* pDoc, SwTableNode* pTableNd, + SwTableLine* pLine, SwTableBoxFormat* pBoxFrameFormat, + SwTableBox* pBox, + sal_uInt16 nInsPos, sal_uInt16 nCnt ) +{ + OSL_ENSURE( pBox->GetSttNd(), "Box with no start node" ); + SwNodeIndex aIdx( *pBox->GetSttNd(), +1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = pDoc->GetNodes().GoNext( &aIdx ); + OSL_ENSURE( pCNd, "Box with no content node" ); + + if( pCNd->IsTextNode() ) + { + if( pBox->GetSaveNumFormatColor() && pCNd->GetpSwAttrSet() ) + { + SwAttrSet aAttrSet( *pCNd->GetpSwAttrSet() ); + if( pBox->GetSaveUserColor() ) + aAttrSet.Put( SvxColorItem( *pBox->GetSaveUserColor(), RES_CHRATR_COLOR )); + else + aAttrSet.ClearItem( RES_CHRATR_COLOR ); + pDoc->GetNodes().InsBoxen( pTableNd, pLine, pBoxFrameFormat, + static_cast<SwTextNode*>(pCNd)->GetTextColl(), + &aAttrSet, nInsPos, nCnt ); + } + else + pDoc->GetNodes().InsBoxen( pTableNd, pLine, pBoxFrameFormat, + static_cast<SwTextNode*>(pCNd)->GetTextColl(), + pCNd->GetpSwAttrSet(), + nInsPos, nCnt ); + } + else + pDoc->GetNodes().InsBoxen( pTableNd, pLine, pBoxFrameFormat, + pDoc->GetDfltTextFormatColl(), nullptr, + nInsPos, nCnt ); + + long nRowSpan = pBox->getRowSpan(); + if( nRowSpan != 1 ) + { + SwTableBoxes& rTableBoxes = pLine->GetTabBoxes(); + for( sal_uInt16 i = 0; i < nCnt; ++i ) + { + pBox = rTableBoxes[ i + nInsPos ]; + pBox->setRowSpan( nRowSpan ); + } + } +} + +SwTable::SwTable() + : SwClient( nullptr ), + m_pTableNode( nullptr ), + m_nGraphicsThatResize( 0 ), + m_nRowsToRepeat( 1 ), + m_bModifyLocked( false ), + m_bNewModel( true ) +{ + // default value set in the options + m_eTableChgMode = GetTableChgDefaultMode(); +} + +SwTable::SwTable( const SwTable& rTable ) + : SwClient( rTable.GetFrameFormat() ), + m_pTableNode( nullptr ), + m_eTableChgMode( rTable.m_eTableChgMode ), + m_nGraphicsThatResize( 0 ), + m_nRowsToRepeat( rTable.GetRowsToRepeat() ), + maTableStyleName(rTable.maTableStyleName), + m_bModifyLocked( false ), + m_bNewModel( rTable.m_bNewModel ) +{ +} + +void DelBoxNode( SwTableSortBoxes const & rSortCntBoxes ) +{ + for (size_t n = 0; n < rSortCntBoxes.size(); ++n) + { + rSortCntBoxes[ n ]->m_pStartNode = nullptr; + } +} + +SwTable::~SwTable() +{ + if( m_xRefObj.is() ) + { + SwDoc* pDoc = GetFrameFormat()->GetDoc(); + if( !pDoc->IsInDtor() ) // then remove from the list + pDoc->getIDocumentLinksAdministration().GetLinkManager().RemoveServer( m_xRefObj.get() ); + + m_xRefObj->Closed(); + } + + // the table can be deleted if it's the last client of the FrameFormat + SwTableFormat* pFormat = GetFrameFormat(); + pFormat->Remove( this ); // remove + + if( !pFormat->HasWriterListeners() ) + pFormat->GetDoc()->DelTableFrameFormat( pFormat ); // and delete + + // Delete the pointers from the SortArray of the boxes. The objects + // are preserved and are deleted by the lines/boxes arrays dtor. + // Note: unfortunately not enough, pointers to the StartNode of the + // section need deletion. + DelBoxNode(m_TabSortContentBoxes); + m_TabSortContentBoxes.clear(); +} + +namespace +{ + +template<class T> +T lcl_MulDiv64(sal_uInt64 nA, sal_uInt64 nM, sal_uInt64 nD) +{ + return static_cast<T>((nA*nM)/nD); +} + +} + +static void FormatInArr( std::vector<SwFormat*>& rFormatArr, SwFormat* pBoxFormat ) +{ + std::vector<SwFormat*>::const_iterator it = std::find( rFormatArr.begin(), rFormatArr.end(), pBoxFormat ); + if ( it == rFormatArr.end() ) + rFormatArr.push_back( pBoxFormat ); +} + +static void lcl_ModifyBoxes( SwTableBoxes &rBoxes, const long nOld, + const long nNew, std::vector<SwFormat*>& rFormatArr ); + +static void lcl_ModifyLines( SwTableLines &rLines, const long nOld, + const long nNew, std::vector<SwFormat*>& rFormatArr, const bool bCheckSum ) +{ + for ( size_t i = 0; i < rLines.size(); ++i ) + ::lcl_ModifyBoxes( rLines[i]->GetTabBoxes(), nOld, nNew, rFormatArr ); + if( bCheckSum ) + { + for(SwFormat* pFormat : rFormatArr) + { + const SwTwips nBox = lcl_MulDiv64<SwTwips>(pFormat->GetFrameSize().GetWidth(), nNew, nOld); + SwFormatFrameSize aNewBox( SwFrameSize::Variable, nBox, 0 ); + pFormat->LockModify(); + pFormat->SetFormatAttr( aNewBox ); + pFormat->UnlockModify(); + } + } +} + +static void lcl_ModifyBoxes( SwTableBoxes &rBoxes, const long nOld, + const long nNew, std::vector<SwFormat*>& rFormatArr ) +{ + sal_uInt64 nSum = 0; // To avoid rounding errors we summarize all box widths + sal_uInt64 nOriginalSum = 0; // Sum of original widths + for ( size_t i = 0; i < rBoxes.size(); ++i ) + { + SwTableBox &rBox = *rBoxes[i]; + if ( !rBox.GetTabLines().empty() ) + { + // For SubTables the rounding problem will not be solved :-( + ::lcl_ModifyLines( rBox.GetTabLines(), nOld, nNew, rFormatArr, false ); + } + // Adjust the box + SwFrameFormat *pFormat = rBox.GetFrameFormat(); + sal_uInt64 nBox = pFormat->GetFrameSize().GetWidth(); + nOriginalSum += nBox; + nBox *= nNew; + nBox /= nOld; + const sal_uInt64 nWishedSum = lcl_MulDiv64<sal_uInt64>(nOriginalSum, nNew, nOld) - nSum; + if( nWishedSum > 0 ) + { + if( nBox == nWishedSum ) + FormatInArr( rFormatArr, pFormat ); + else + { + nBox = nWishedSum; + pFormat = rBox.ClaimFrameFormat(); + SwFormatFrameSize aNewBox( SwFrameSize::Variable, static_cast< SwTwips >(nBox), 0 ); + pFormat->LockModify(); + pFormat->SetFormatAttr( aNewBox ); + pFormat->UnlockModify(); + } + } + else { + OSL_FAIL( "Rounding error" ); + } + nSum += nBox; + } +} + +void SwTable::Modify( const SfxPoolItem* pOld, const SfxPoolItem *pNew ) +{ + // catch SSize changes, to adjust the lines/boxes + const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0 ; + const SwFormatFrameSize* pNewSize = nullptr, *pOldSize = nullptr; + + if( RES_ATTRSET_CHG == nWhich ) + { + if (pOld && pNew && SfxItemState::SET == static_cast<const SwAttrSetChg*>(pNew)->GetChgSet()->GetItemState( + RES_FRM_SIZE, false, reinterpret_cast<const SfxPoolItem**>(&pNewSize))) + { + pOldSize = &static_cast<const SwAttrSetChg*>(pOld)->GetChgSet()->GetFrameSize(); + } + } + else if( RES_FRM_SIZE == nWhich ) + { + pOldSize = static_cast<const SwFormatFrameSize*>(pOld); + pNewSize = static_cast<const SwFormatFrameSize*>(pNew); + } + else + CheckRegistration( pOld ); + + if (pOldSize && pNewSize && !m_bModifyLocked) + AdjustWidths( pOldSize->GetWidth(), pNewSize->GetWidth() ); +} + +void SwTable::AdjustWidths( const long nOld, const long nNew ) +{ + std::vector<SwFormat*> aFormatArr; + aFormatArr.reserve( m_aLines[0]->GetTabBoxes().size() ); + ::lcl_ModifyLines( m_aLines, nOld, nNew, aFormatArr, true ); +} + +static void lcl_RefreshHidden( SwTabCols &rToFill, size_t nPos ) +{ + for ( size_t i = 0; i < rToFill.Count(); ++i ) + { + if ( std::abs(static_cast<long>(nPos) - rToFill[i]) <= COLFUZZY ) + { + rToFill.SetHidden( i, false ); + break; + } + } +} + +static void lcl_SortedTabColInsert( SwTabCols &rToFill, const SwTableBox *pBox, + const SwFrameFormat *pTabFormat, const bool bHidden, + const bool bRefreshHidden ) +{ + const long nWish = pTabFormat->GetFrameSize().GetWidth(); + OSL_ENSURE(nWish, "weird <= 0 width frmfrm"); + + // The value for the left edge of the box is calculated from the + // widths of the previous boxes. + long nPos = 0; + long nLeftMin = 0; + long nRightMax = 0; + if (nWish != 0) //fdo#33012 0 width frmfmt + { + SwTwips nSum = 0; + const SwTableBox *pCur = pBox; + const SwTableLine *pLine = pBox->GetUpper(); + const long nAct = rToFill.GetRight() - rToFill.GetLeft(); // +1 why? + + while ( pLine ) + { + const SwTableBoxes &rBoxes = pLine->GetTabBoxes(); + for ( size_t i = 0; i < rBoxes.size(); ++i ) + { + const SwTwips nWidth = rBoxes[i]->GetFrameFormat()->GetFrameSize().GetWidth(); + nSum += nWidth; + const long nTmp = lcl_MulDiv64<long>(nSum, nAct, nWish); + + if (rBoxes[i] != pCur) + { + if ( pLine == pBox->GetUpper() || 0 == nLeftMin ) + nLeftMin = nTmp - nPos; + nPos = nTmp; + } + else + { + nSum -= nWidth; + if ( 0 == nRightMax ) + nRightMax = nTmp - nPos; + break; + } + } + pCur = pLine->GetUpper(); + pLine = pCur ? pCur->GetUpper() : nullptr; + } + } + + bool bInsert = !bRefreshHidden; + for ( size_t j = 0; bInsert && (j < rToFill.Count()); ++j ) + { + long nCmp = rToFill[j]; + if ( (nPos >= ((nCmp >= COLFUZZY) ? nCmp - COLFUZZY : nCmp)) && + (nPos <= (nCmp + COLFUZZY)) ) + { + bInsert = false; // Already has it. + } + else if ( nPos < nCmp ) + { + bInsert = false; + rToFill.Insert( nPos, bHidden, j ); + } + } + if ( bInsert ) + rToFill.Insert( nPos, bHidden, rToFill.Count() ); + else if ( bRefreshHidden ) + ::lcl_RefreshHidden( rToFill, nPos ); + + if ( bHidden && !bRefreshHidden ) + { + // calculate minimum/maximum values for the existing entries: + nLeftMin = nPos - nLeftMin; + nRightMax = nPos + nRightMax; + + // check if nPos is entry: + bool bFoundPos = false; + bool bFoundMax = false; + for ( size_t j = 0; !(bFoundPos && bFoundMax ) && j < rToFill.Count(); ++j ) + { + SwTabColsEntry& rEntry = rToFill.GetEntry( j ); + long nCmp = rToFill[j]; + + if ( (nPos >= ((nCmp >= COLFUZZY) ? nCmp - COLFUZZY : nCmp)) && + (nPos <= (nCmp + COLFUZZY)) ) + { + // check if nLeftMin is > old minimum for entry nPos: + const long nOldMin = rEntry.nMin; + if ( nLeftMin > nOldMin ) + rEntry.nMin = nLeftMin; + // check if nRightMin is < old maximum for entry nPos: + const long nOldMax = rEntry.nMax; + if ( nRightMax < nOldMax ) + rEntry.nMax = nRightMax; + + bFoundPos = true; + } + else if ( (nRightMax >= ((nCmp >= COLFUZZY) ? nCmp - COLFUZZY : nCmp)) && + (nRightMax <= (nCmp + COLFUZZY)) ) + { + // check if nPos is > old minimum for entry nRightMax: + const long nOldMin = rEntry.nMin; + if ( nPos > nOldMin ) + rEntry.nMin = nPos; + + bFoundMax = true; + } + } + } +} + +static void lcl_ProcessBoxGet( const SwTableBox *pBox, SwTabCols &rToFill, + const SwFrameFormat *pTabFormat, bool bRefreshHidden ) +{ + if ( !pBox->GetTabLines().empty() ) + { + const SwTableLines &rLines = pBox->GetTabLines(); + for ( size_t i = 0; i < rLines.size(); ++i ) + { + const SwTableBoxes &rBoxes = rLines[i]->GetTabBoxes(); + for ( size_t j = 0; j < rBoxes.size(); ++j ) + ::lcl_ProcessBoxGet( rBoxes[j], rToFill, pTabFormat, bRefreshHidden); + } + } + else + ::lcl_SortedTabColInsert( rToFill, pBox, pTabFormat, false, bRefreshHidden ); +} + +static void lcl_ProcessLineGet( const SwTableLine *pLine, SwTabCols &rToFill, + const SwFrameFormat *pTabFormat ) +{ + for ( size_t i = 0; i < pLine->GetTabBoxes().size(); ++i ) + { + const SwTableBox *pBox = pLine->GetTabBoxes()[i]; + if ( pBox->GetSttNd() ) + ::lcl_SortedTabColInsert( rToFill, pBox, pTabFormat, true, false ); + else + for ( size_t j = 0; j < pBox->GetTabLines().size(); ++j ) + ::lcl_ProcessLineGet( pBox->GetTabLines()[j], rToFill, pTabFormat ); + } +} + +void SwTable::GetTabCols( SwTabCols &rToFill, const SwTableBox *pStart, + bool bRefreshHidden, bool bCurRowOnly ) const +{ + // Optimization: if bHidden is set, we only update the Hidden Array. + if ( bRefreshHidden ) + { + // remove corrections + for ( size_t i = 0; i < rToFill.Count(); ++i ) + { + SwTabColsEntry& rEntry = rToFill.GetEntry( i ); + rEntry.nPos -= rToFill.GetLeft(); + rEntry.nMin -= rToFill.GetLeft(); + rEntry.nMax -= rToFill.GetLeft(); + } + + // All are hidden, so add the visible ones. + for ( size_t i = 0; i < rToFill.Count(); ++i ) + rToFill.SetHidden( i, true ); + } + else + { + rToFill.Remove( 0, rToFill.Count() ); + } + + // Insertion cases: + // 1. All boxes which are inferior to Line which is superior to the Start, + // as well as their inferior boxes if present. + // 2. Starting from the Line, the superior box plus its neighbours; but no inferiors. + // 3. Apply 2. to the Line superior to the chain of boxes, + // until the Line's superior is not a box but the table. + // Only those boxes are inserted that don't contain further rows. The insertion + // function takes care to avoid duplicates. In order to achieve this, we work + // with some degree of fuzzyness (to avoid rounding errors). + // Only the left edge of the boxes are inserted. + // Finally, the first entry is removed again, because it's already + // covered by the border. + // 4. Scan the table again and insert _all_ boxes, this time as hidden. + + const SwFrameFormat *pTabFormat = GetFrameFormat(); + + // 1. + const SwTableBoxes &rBoxes = pStart->GetUpper()->GetTabBoxes(); + + for ( size_t i = 0; i < rBoxes.size(); ++i ) + ::lcl_ProcessBoxGet( rBoxes[i], rToFill, pTabFormat, bRefreshHidden ); + + // 2. and 3. + const SwTableLine *pLine = pStart->GetUpper()->GetUpper() ? + pStart->GetUpper()->GetUpper()->GetUpper() : nullptr; + while ( pLine ) + { + const SwTableBoxes &rBoxes2 = pLine->GetTabBoxes(); + for ( size_t k = 0; k < rBoxes2.size(); ++k ) + ::lcl_SortedTabColInsert( rToFill, rBoxes2[k], + pTabFormat, false, bRefreshHidden ); + pLine = pLine->GetUpper() ? pLine->GetUpper()->GetUpper() : nullptr; + } + + if ( !bRefreshHidden ) + { + // 4. + if ( !bCurRowOnly ) + { + for ( size_t i = 0; i < m_aLines.size(); ++i ) + ::lcl_ProcessLineGet( m_aLines[i], rToFill, pTabFormat ); + } + + rToFill.Remove( 0 ); + } + + // Now the coordinates are relative to the left table border - i.e. + // relative to SwTabCols.nLeft. However, they are expected + // relative to the left document border, i.e. SwTabCols.nLeftMin. + // So all values need to be extended by nLeft. + for ( size_t i = 0; i < rToFill.Count(); ++i ) + { + SwTabColsEntry& rEntry = rToFill.GetEntry( i ); + rEntry.nPos += rToFill.GetLeft(); + rEntry.nMin += rToFill.GetLeft(); + rEntry.nMax += rToFill.GetLeft(); + } +} + +// Structure for parameter passing +struct Parm +{ + const SwTabCols &rNew; + const SwTabCols &rOld; + long nNewWish, + nOldWish; + std::deque<SwTableBox*> aBoxArr; + SwShareBoxFormats aShareFormats; + + Parm( const SwTabCols &rN, const SwTabCols &rO ) + : rNew( rN ), rOld( rO ), nNewWish(0), nOldWish(0) + {} +}; + +static void lcl_ProcessBoxSet( SwTableBox *pBox, Parm &rParm ); + +static void lcl_ProcessLine( SwTableLine *pLine, Parm &rParm ) +{ + SwTableBoxes &rBoxes = pLine->GetTabBoxes(); + for ( size_t i = rBoxes.size(); i > 0; ) + { + --i; + ::lcl_ProcessBoxSet( rBoxes[i], rParm ); + } +} + +static void lcl_ProcessBoxSet( SwTableBox *pBox, Parm &rParm ) +{ + if ( !pBox->GetTabLines().empty() ) + { + SwTableLines &rLines = pBox->GetTabLines(); + for ( size_t i = rLines.size(); i > 0; ) + { + --i; + lcl_ProcessLine( rLines[i], rParm ); + } + } + else + { + // Search the old TabCols for the current position (calculate from + // left and right edge). Adjust the box if the values differ from + // the new TabCols. If the adjusted edge has no neighbour we also + // adjust all superior boxes. + + const long nOldAct = rParm.rOld.GetRight() - + rParm.rOld.GetLeft(); // +1 why? + + // The value for the left edge of the box is calculated from the + // widths of the previous boxes plus the left edge. + long nLeft = rParm.rOld.GetLeft(); + const SwTableBox *pCur = pBox; + const SwTableLine *pLine = pBox->GetUpper(); + + while ( pLine ) + { + const SwTableBoxes &rBoxes = pLine->GetTabBoxes(); + for ( size_t i = 0; (i < rBoxes.size()) && (rBoxes[i] != pCur); ++i) + { + nLeft += lcl_MulDiv64<long>( + rBoxes[i]->GetFrameFormat()->GetFrameSize().GetWidth(), + nOldAct, rParm.nOldWish); + } + pCur = pLine->GetUpper(); + pLine = pCur ? pCur->GetUpper() : nullptr; + } + long nLeftDiff = 0; + long nRightDiff = 0; + if ( nLeft != rParm.rOld.GetLeft() ) // There are still boxes before this. + { + // Right edge is left edge plus width. + const long nWidth = lcl_MulDiv64<long>( + pBox->GetFrameFormat()->GetFrameSize().GetWidth(), + nOldAct, rParm.nOldWish); + const long nRight = nLeft + nWidth; + size_t nLeftPos = 0; + size_t nRightPos = 0; + bool bFoundLeftPos = false; + bool bFoundRightPos = false; + for ( size_t i = 0; i < rParm.rOld.Count(); ++i ) + { + if ( nLeft >= (rParm.rOld[i] - COLFUZZY) && + nLeft <= (rParm.rOld[i] + COLFUZZY) ) + { + nLeftPos = i; + bFoundLeftPos = true; + } + else if ( nRight >= (rParm.rOld[i] - COLFUZZY) && + nRight <= (rParm.rOld[i] + COLFUZZY) ) + { + nRightPos = i; + bFoundRightPos = true; + } + } + nLeftDiff = bFoundLeftPos ? + rParm.rOld[nLeftPos] - rParm.rNew[nLeftPos] : 0; + nRightDiff= bFoundRightPos ? + rParm.rNew[nRightPos] - rParm.rOld[nRightPos] : 0; + } + else // The first box. + { + nLeftDiff = rParm.rOld.GetLeft() - rParm.rNew.GetLeft(); + if ( rParm.rOld.Count() ) + { + // Calculate the difference to the edge touching the first box. + const long nWidth = lcl_MulDiv64<long>( + pBox->GetFrameFormat()->GetFrameSize().GetWidth(), + nOldAct, rParm.nOldWish); + const long nTmp = nWidth + rParm.rOld.GetLeft(); + for ( size_t i = 0; i < rParm.rOld.Count(); ++i ) + { + if ( nTmp >= (rParm.rOld[i] - COLFUZZY) && + nTmp <= (rParm.rOld[i] + COLFUZZY) ) + { + nRightDiff = rParm.rNew[i] - rParm.rOld[i]; + break; + } + } + } + } + + if( pBox->getRowSpan() == 1 ) + { + const sal_uInt16 nPos = pBox->GetUpper()->GetBoxPos( pBox ); + SwTableBoxes& rTableBoxes = pBox->GetUpper()->GetTabBoxes(); + if( nPos && rTableBoxes[ nPos - 1 ]->getRowSpan() != 1 ) + nLeftDiff = 0; + if( nPos + 1 < static_cast<sal_uInt16>(rTableBoxes.size()) && + rTableBoxes[ nPos + 1 ]->getRowSpan() != 1 ) + nRightDiff = 0; + } + else + nLeftDiff = nRightDiff = 0; + + if ( nLeftDiff || nRightDiff ) + { + // The difference is the actual difference amount. For stretched + // tables, it does not make sense to adjust the attributes of the + // boxes by this amount. The difference amount needs to be converted + // accordingly. + long nTmp = rParm.rNew.GetRight() - rParm.rNew.GetLeft(); // +1 why? + nLeftDiff *= rParm.nNewWish; + nLeftDiff /= nTmp; + nRightDiff *= rParm.nNewWish; + nRightDiff /= nTmp; + long nDiff = nLeftDiff + nRightDiff; + + // Adjust the box and all superiors by the difference amount. + while ( pBox ) + { + SwFormatFrameSize aFormatFrameSize( pBox->GetFrameFormat()->GetFrameSize() ); + aFormatFrameSize.SetWidth( aFormatFrameSize.GetWidth() + nDiff ); + if ( aFormatFrameSize.GetWidth() < 0 ) + aFormatFrameSize.SetWidth( -aFormatFrameSize.GetWidth() ); + rParm.aShareFormats.SetSize( *pBox, aFormatFrameSize ); + + // The outer cells of the last row are responsible to adjust a surrounding cell. + // Last line check: + if ( pBox->GetUpper()->GetUpper() && + pBox->GetUpper() != pBox->GetUpper()->GetUpper()->GetTabLines().back()) + { + pBox = nullptr; + } + else + { + // Middle cell check: + if ( pBox != pBox->GetUpper()->GetTabBoxes().front() ) + nDiff = nRightDiff; + + if ( pBox != pBox->GetUpper()->GetTabBoxes().back() ) + nDiff -= nRightDiff; + + pBox = nDiff ? pBox->GetUpper()->GetUpper() : nullptr; + } + } + } + } +} + +static void lcl_ProcessBoxPtr( SwTableBox *pBox, std::deque<SwTableBox*> &rBoxArr, + bool bBefore ) +{ + if ( !pBox->GetTabLines().empty() ) + { + const SwTableLines &rLines = pBox->GetTabLines(); + for ( size_t i = 0; i < rLines.size(); ++i ) + { + const SwTableBoxes &rBoxes = rLines[i]->GetTabBoxes(); + for ( size_t j = 0; j < rBoxes.size(); ++j ) + ::lcl_ProcessBoxPtr( rBoxes[j], rBoxArr, bBefore ); + } + } + else if ( bBefore ) + rBoxArr.push_front( pBox ); + else + rBoxArr.push_back( pBox ); +} + +static void lcl_AdjustBox( SwTableBox *pBox, const long nDiff, Parm &rParm ); + +static void lcl_AdjustLines( SwTableLines &rLines, const long nDiff, Parm &rParm ) +{ + for ( size_t i = 0; i < rLines.size(); ++i ) + { + SwTableBox *pBox = rLines[i]->GetTabBoxes() + [rLines[i]->GetTabBoxes().size()-1]; + lcl_AdjustBox( pBox, nDiff, rParm ); + } +} + +static void lcl_AdjustBox( SwTableBox *pBox, const long nDiff, Parm &rParm ) +{ + if ( !pBox->GetTabLines().empty() ) + ::lcl_AdjustLines( pBox->GetTabLines(), nDiff, rParm ); + + // Adjust the size of the box. + SwFormatFrameSize aFormatFrameSize( pBox->GetFrameFormat()->GetFrameSize() ); + aFormatFrameSize.SetWidth( aFormatFrameSize.GetWidth() + nDiff ); + + rParm.aShareFormats.SetSize( *pBox, aFormatFrameSize ); +} + +void SwTable::SetTabCols( const SwTabCols &rNew, const SwTabCols &rOld, + const SwTableBox *pStart, bool bCurRowOnly ) +{ + CHECK_TABLE( *this ) + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // delete HTML-Layout + + // FME: Made rOld const. The caller is responsible for passing correct + // values of rOld. Therefore we do not have to call GetTabCols anymore: + //GetTabCols( rOld, pStart ); + + Parm aParm( rNew, rOld ); + + OSL_ENSURE( rOld.Count() == rNew.Count(), "Number of columns changed."); + + // Convert the edges. We need to adjust the size of the table and some boxes. + // For the size adjustment, we must not make use of the Modify, since that'd + // adjust all boxes, which we really don't want. + SwFrameFormat *pFormat = GetFrameFormat(); + aParm.nOldWish = aParm.nNewWish = pFormat->GetFrameSize().GetWidth(); + if ( (rOld.GetLeft() != rNew.GetLeft()) || + (rOld.GetRight()!= rNew.GetRight()) ) + { + LockModify(); + { + SvxLRSpaceItem aLR( pFormat->GetLRSpace() ); + SvxShadowItem aSh( pFormat->GetShadow() ); + + SwTwips nShRight = aSh.CalcShadowSpace( SvxShadowItemSide::RIGHT ); + SwTwips nShLeft = aSh.CalcShadowSpace( SvxShadowItemSide::LEFT ); + + aLR.SetLeft ( rNew.GetLeft() - nShLeft ); + aLR.SetRight( rNew.GetRightMax() - rNew.GetRight() - nShRight ); + pFormat->SetFormatAttr( aLR ); + + // The alignment of the table needs to be adjusted accordingly. + // This is done by preserving the exact positions that have been + // set by the user. + SwFormatHoriOrient aOri( pFormat->GetHoriOrient() ); + if(text::HoriOrientation::NONE != aOri.GetHoriOrient()) + { + const bool bLeftDist = rNew.GetLeft() != nShLeft; + const bool bRightDist = rNew.GetRight() + nShRight != rNew.GetRightMax(); + if(!bLeftDist && !bRightDist) + aOri.SetHoriOrient( text::HoriOrientation::FULL ); + else if(!bRightDist && rNew.GetLeft() > nShLeft ) + aOri.SetHoriOrient( text::HoriOrientation::RIGHT ); + else if(!bLeftDist && rNew.GetRight() + nShRight < rNew.GetRightMax()) + aOri.SetHoriOrient( text::HoriOrientation::LEFT ); + else + aOri.SetHoriOrient( text::HoriOrientation::LEFT_AND_WIDTH ); + } + pFormat->SetFormatAttr( aOri ); + } + const long nAct = rOld.GetRight() - rOld.GetLeft(); // +1 why? + long nTabDiff = 0; + + if ( rOld.GetLeft() != rNew.GetLeft() ) + { + nTabDiff = rOld.GetLeft() - rNew.GetLeft(); + nTabDiff *= aParm.nOldWish; + nTabDiff /= nAct; + } + if ( rOld.GetRight() != rNew.GetRight() ) + { + long nDiff = rNew.GetRight() - rOld.GetRight(); + nDiff *= aParm.nOldWish; + nDiff /= nAct; + nTabDiff += nDiff; + if( !IsNewModel() ) + ::lcl_AdjustLines( GetTabLines(), nDiff, aParm ); + } + + // Adjust the size of the table, watch out for stretched tables. + if ( nTabDiff ) + { + aParm.nNewWish += nTabDiff; + if ( aParm.nNewWish < 0 ) + aParm.nNewWish = USHRT_MAX; // Oops! Have to roll back. + SwFormatFrameSize aSz( pFormat->GetFrameSize() ); + if ( aSz.GetWidth() != aParm.nNewWish ) + { + aSz.SetWidth( aParm.nNewWish ); + aSz.SetWidthPercent( 0 ); + pFormat->SetFormatAttr( aSz ); + } + } + UnlockModify(); + } + + if( IsNewModel() ) + NewSetTabCols( aParm, rNew, rOld, pStart, bCurRowOnly ); + else + { + if ( bCurRowOnly ) + { + // To adjust the current row, we need to process all its boxes, + // similar to the filling of the TabCols (see GetTabCols()). + // Unfortunately we again have to take care to adjust the boxes + // from back to front, respectively from outer to inner. + // The best way to achieve this is probably to track the boxes + // in a PtrArray. + const SwTableBoxes &rBoxes = pStart->GetUpper()->GetTabBoxes(); + for ( size_t i = 0; i < rBoxes.size(); ++i ) + ::lcl_ProcessBoxPtr( rBoxes[i], aParm.aBoxArr, false ); + + const SwTableLine *pLine = pStart->GetUpper()->GetUpper() ? + pStart->GetUpper()->GetUpper()->GetUpper() : nullptr; + const SwTableBox *pExcl = pStart->GetUpper()->GetUpper(); + while ( pLine ) + { + const SwTableBoxes &rBoxes2 = pLine->GetTabBoxes(); + bool bBefore = true; + for ( size_t i = 0; i < rBoxes2.size(); ++i ) + { + if ( rBoxes2[i] != pExcl ) + ::lcl_ProcessBoxPtr( rBoxes2[i], aParm.aBoxArr, bBefore ); + else + bBefore = false; + } + pExcl = pLine->GetUpper(); + pLine = pLine->GetUpper() ? pLine->GetUpper()->GetUpper() : nullptr; + } + // After we've inserted a bunch of boxes (hopefully all and in + // correct order), we just need to process them in reverse order. + for ( int j = aParm.aBoxArr.size()-1; j >= 0; --j ) + { + SwTableBox *pBox = aParm.aBoxArr[j]; + ::lcl_ProcessBoxSet( pBox, aParm ); + } + } + else + { + // Adjusting the entire table is 'easy'. All boxes without lines are + // adjusted, as are their superiors. Of course we need to process + // in reverse order to prevent fooling ourselves! + SwTableLines &rLines = GetTabLines(); + for ( size_t i = rLines.size(); i > 0; ) + { + --i; + ::lcl_ProcessLine( rLines[i], aParm ); + } + } + } + +#ifdef DBG_UTIL + { + // do some checking for correct table widths + SwTwips nSize = GetFrameFormat()->GetFrameSize().GetWidth(); + for (size_t n = 0; n < m_aLines.size(); ++n) + { + CheckBoxWidth( *m_aLines[ n ], nSize ); + } + } +#endif +} + +typedef std::pair<sal_uInt16, sal_uInt16> ColChange; +typedef std::list< ColChange > ChangeList; + +static void lcl_AdjustWidthsInLine( SwTableLine* pLine, ChangeList& rOldNew, + Parm& rParm, sal_uInt16 nColFuzzy ) +{ + ChangeList::iterator pCurr = rOldNew.begin(); + if( pCurr == rOldNew.end() ) + return; + const size_t nCount = pLine->GetTabBoxes().size(); + SwTwips nBorder = 0; + SwTwips nRest = 0; + for( size_t i = 0; i < nCount; ++i ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[i]; + SwTwips nWidth = pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + SwTwips nNewWidth = nWidth - nRest; + nRest = 0; + nBorder += nWidth; + if( pCurr != rOldNew.end() && nBorder + nColFuzzy >= pCurr->first ) + { + nBorder -= nColFuzzy; + while( pCurr != rOldNew.end() && nBorder > pCurr->first ) + ++pCurr; + if( pCurr != rOldNew.end() ) + { + nBorder += nColFuzzy; + if( nBorder + nColFuzzy >= pCurr->first ) + { + if( pCurr->second == pCurr->first ) + nRest = 0; + else + nRest = pCurr->second - nBorder; + nNewWidth += nRest; + ++pCurr; + } + } + } + if( nNewWidth != nWidth ) + { + if( nNewWidth < 0 ) + { + nRest += 1 - nNewWidth; + nNewWidth = 1; + } + SwFormatFrameSize aFormatFrameSize( pBox->GetFrameFormat()->GetFrameSize() ); + aFormatFrameSize.SetWidth( nNewWidth ); + rParm.aShareFormats.SetSize( *pBox, aFormatFrameSize ); + } + } +} + +static void lcl_CalcNewWidths( std::list<sal_uInt16> &rSpanPos, ChangeList& rChanges, + SwTableLine* pLine, long nWish, long nWidth, bool bTop ) +{ + if( rChanges.empty() ) + { + rSpanPos.clear(); + return; + } + if( rSpanPos.empty() ) + { + rChanges.clear(); + return; + } + std::list<sal_uInt16> aNewSpanPos; + ChangeList aNewChanges; + ChangeList::iterator pCurr = rChanges.begin(); + aNewChanges.push_back( *pCurr ); // Nullposition + std::list<sal_uInt16>::iterator pSpan = rSpanPos.begin(); + sal_uInt16 nCurr = 0; + SwTwips nOrgSum = 0; + bool bRowSpan = false; + sal_uInt16 nRowSpanCount = 0; + const size_t nCount = pLine->GetTabBoxes().size(); + for( size_t nCurrBox = 0; nCurrBox < nCount; ++nCurrBox ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrBox]; + SwTwips nCurrWidth = pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + const long nRowSpan = pBox->getRowSpan(); + const bool bCurrRowSpan = bTop ? nRowSpan < 0 : + ( nRowSpan > 1 || nRowSpan < -1 ); + if( bRowSpan || bCurrRowSpan ) + aNewSpanPos.push_back( nRowSpanCount ); + bRowSpan = bCurrRowSpan; + nOrgSum += nCurrWidth; + const sal_uInt16 nPos = lcl_MulDiv64<sal_uInt16>( + lcl_MulDiv64<sal_uInt64>(nOrgSum, nWidth, nWish), + nWish, nWidth); + while( pCurr != rChanges.end() && pCurr->first < nPos ) + { + ++nCurr; + ++pCurr; + } + bool bNew = true; + if( pCurr != rChanges.end() && pCurr->first <= nPos && + pCurr->first != pCurr->second ) + { + pSpan = std::find_if(pSpan, rSpanPos.end(), + [nCurr](const sal_uInt16 nSpan) { return nSpan >= nCurr; }); + if( pSpan != rSpanPos.end() && *pSpan == nCurr ) + { + aNewChanges.push_back( *pCurr ); + ++nRowSpanCount; + bNew = false; + } + } + if( bNew ) + { + ColChange aTmp( nPos, nPos ); + aNewChanges.push_back( aTmp ); + ++nRowSpanCount; + } + } + + pCurr = aNewChanges.begin(); + ChangeList::iterator pLast = pCurr; + ChangeList::iterator pLeftMove = pCurr; + while( pCurr != aNewChanges.end() ) + { + if( pLeftMove == pCurr ) + { + while( ++pLeftMove != aNewChanges.end() && pLeftMove->first <= pLeftMove->second ) + ; + } + if( pCurr->second == pCurr->first ) + { + if( pLeftMove != aNewChanges.end() && pCurr->second > pLeftMove->second ) + { + if( pLeftMove->first == pLast->first ) + pCurr->second = pLeftMove->second; + else + { + pCurr->second = lcl_MulDiv64<sal_uInt16>( + pCurr->first - pLast->first, + pLeftMove->second - pLast->second, + pLeftMove->first - pLast->first) + pLast->second; + } + } + pLast = pCurr; + ++pCurr; + } + else if( pCurr->second > pCurr->first ) + { + pLast = pCurr; + ++pCurr; + ChangeList::iterator pNext = pCurr; + while( pNext != pLeftMove && pNext->second == pNext->first && + pNext->second < pLast->second ) + ++pNext; + while( pCurr != pNext ) + { + if( pNext == aNewChanges.end() || pNext->first == pLast->first ) + pCurr->second = pLast->second; + else + { + pCurr->second = lcl_MulDiv64<sal_uInt16>( + pCurr->first - pLast->first, + pNext->second - pLast->second, + pNext->first - pLast->first) + pLast->second; + } + ++pCurr; + } + pLast = pCurr; + } + else + { + pLast = pCurr; + ++pCurr; + } + } + + rChanges.swap(aNewChanges); + rSpanPos.swap(aNewSpanPos); +} + +void SwTable::NewSetTabCols( Parm &rParm, const SwTabCols &rNew, + const SwTabCols &rOld, const SwTableBox *pStart, bool bCurRowOnly ) +{ +#if OSL_DEBUG_LEVEL > 1 + static int nCallCount = 0; + ++nCallCount; +#endif + // First step: evaluate which lines have been moved/which widths changed + ChangeList aOldNew; + const long nNewWidth = rParm.rNew.GetRight() - rParm.rNew.GetLeft(); + const long nOldWidth = rParm.rOld.GetRight() - rParm.rOld.GetLeft(); + if( nNewWidth < 1 || nOldWidth < 1 ) + return; + for( size_t i = 0; i <= rOld.Count(); ++i ) + { + long nNewPos; + long nOldPos; + if( i == rOld.Count() ) + { + nOldPos = rParm.rOld.GetRight() - rParm.rOld.GetLeft(); + nNewPos = rParm.rNew.GetRight() - rParm.rNew.GetLeft(); + } + else + { + nOldPos = rOld[i] - rParm.rOld.GetLeft(); + nNewPos = rNew[i] - rParm.rNew.GetLeft(); + } + nNewPos = lcl_MulDiv64<long>(nNewPos, rParm.nNewWish, nNewWidth); + nOldPos = lcl_MulDiv64<long>(nOldPos, rParm.nOldWish, nOldWidth); + if( nOldPos != nNewPos && nNewPos > 0 && nOldPos > 0 ) + { + ColChange aChg( static_cast<sal_uInt16>(nOldPos), static_cast<sal_uInt16>(nNewPos) ); + aOldNew.push_back( aChg ); + } + } + // Finished first step + int nCount = aOldNew.size(); + if( !nCount ) + return; // no change, nothing to do + SwTableLines &rLines = GetTabLines(); + if( bCurRowOnly ) + { + const SwTableLine* pCurrLine = pStart->GetUpper(); + sal_uInt16 nCurr = rLines.GetPos( pCurrLine ); + if( nCurr >= USHRT_MAX ) + return; + + ColChange aChg( 0, 0 ); + aOldNew.push_front( aChg ); + std::list<sal_uInt16> aRowSpanPos; + if( nCurr ) + { + ChangeList aCopy; + sal_uInt16 nPos = 0; + for( const auto& rCop : aOldNew ) + { + aCopy.push_back( rCop ); + aRowSpanPos.push_back( nPos++ ); + } + lcl_CalcNewWidths( aRowSpanPos, aCopy, rLines[nCurr], + rParm.nOldWish, nOldWidth, true ); + bool bGoOn = !aRowSpanPos.empty(); + sal_uInt16 j = nCurr; + while( bGoOn ) + { + lcl_CalcNewWidths( aRowSpanPos, aCopy, rLines[--j], + rParm.nOldWish, nOldWidth, true ); + lcl_AdjustWidthsInLine( rLines[j], aCopy, rParm, 0 ); + bGoOn = !aRowSpanPos.empty() && j > 0; + } + aRowSpanPos.clear(); + } + if( nCurr+1 < static_cast<sal_uInt16>(rLines.size()) ) + { + ChangeList aCopy; + sal_uInt16 nPos = 0; + for( const auto& rCop : aOldNew ) + { + aCopy.push_back( rCop ); + aRowSpanPos.push_back( nPos++ ); + } + lcl_CalcNewWidths( aRowSpanPos, aCopy, rLines[nCurr], + rParm.nOldWish, nOldWidth, false ); + bool bGoOn = !aRowSpanPos.empty(); + sal_uInt16 j = nCurr; + while( bGoOn ) + { + lcl_CalcNewWidths( aRowSpanPos, aCopy, rLines[++j], + rParm.nOldWish, nOldWidth, false ); + lcl_AdjustWidthsInLine( rLines[j], aCopy, rParm, 0 ); + bGoOn = !aRowSpanPos.empty() && j+1 < static_cast<sal_uInt16>(rLines.size()); + } + } + ::lcl_AdjustWidthsInLine( rLines[nCurr], aOldNew, rParm, COLFUZZY ); + } + else + { + for( size_t i = 0; i < rLines.size(); ++i ) + ::lcl_AdjustWidthsInLine( rLines[i], aOldNew, rParm, COLFUZZY ); + } + CHECK_TABLE( *this ) +} + +// return the pointer of the box specified. +static bool lcl_IsValidRowName( const OUString& rStr ) +{ + bool bIsValid = true; + sal_Int32 nLen = rStr.getLength(); + for( sal_Int32 i = 0; i < nLen && bIsValid; ++i ) + { + const sal_Unicode cChar = rStr[i]; + if (cChar < '0' || cChar > '9') + bIsValid = false; + } + return bIsValid; +} + +// #i80314# +// add 3rd parameter and its handling +sal_uInt16 SwTable::GetBoxNum( OUString& rStr, bool bFirstPart, + const bool bPerformValidCheck ) +{ + sal_uInt16 nRet = 0; + if( bFirstPart ) // true == column; false == row + { + sal_Int32 nPos = 0; + // the first one uses letters for addressing! + bool bFirst = true; + sal_uInt32 num = 0; + bool overflow = false; + while (nPos<rStr.getLength()) + { + sal_Unicode cChar = rStr[nPos]; + if ((cChar<'A' || cChar>'Z') && (cChar<'a' || cChar>'z')) + break; + if( (cChar -= 'A') >= 26 ) + cChar -= 'a' - '['; + if( bFirst ) + bFirst = false; + else + ++num; + num = num * 52 + cChar; + if (num > SAL_MAX_UINT16) { + overflow = true; + } + ++nPos; + } + nRet = overflow ? SAL_MAX_UINT16 : num; + rStr = rStr.copy( nPos ); // Remove char from String + } + else + { + const sal_Int32 nPos = rStr.indexOf( "." ); + if ( nPos<0 ) + { + nRet = 0; + if ( !bPerformValidCheck || lcl_IsValidRowName( rStr ) ) + { + nRet = static_cast<sal_uInt16>(rStr.toInt32()); + } + rStr.clear(); + } + else + { + nRet = 0; + const OUString aText( rStr.copy( 0, nPos ) ); + if ( !bPerformValidCheck || lcl_IsValidRowName( aText ) ) + { + nRet = static_cast<sal_uInt16>(aText.toInt32()); + } + rStr = rStr.copy( nPos+1 ); + } + } + return nRet; +} + +// #i80314# +// add 2nd parameter and its handling +const SwTableBox* SwTable::GetTableBox( const OUString& rName, + const bool bPerformValidCheck ) const +{ + const SwTableBox* pBox = nullptr; + const SwTableLine* pLine; + const SwTableLines* pLines; + + sal_uInt16 nLine, nBox; + OUString aNm( rName ); + while( !aNm.isEmpty() ) + { + nBox = SwTable::GetBoxNum( aNm, nullptr == pBox, bPerformValidCheck ); + // first box ? + if( !pBox ) + pLines = &GetTabLines(); + else + { + pLines = &pBox->GetTabLines(); + if( nBox ) + --nBox; + } + + nLine = SwTable::GetBoxNum( aNm, false, bPerformValidCheck ); + + // determine line + if( !nLine || nLine > pLines->size() ) + return nullptr; + pLine = (*pLines)[ nLine-1 ]; + + // determine box + const SwTableBoxes* pBoxes = &pLine->GetTabBoxes(); + if( nBox >= pBoxes->size() ) + return nullptr; + pBox = (*pBoxes)[ nBox ]; + } + + // check if the box found has any contents + if( pBox && !pBox->GetSttNd() ) + { + OSL_FAIL( "Box without content, looking for the next one!" ); + // "drop this" until the first box + while( !pBox->GetTabLines().empty() ) + pBox = pBox->GetTabLines().front()->GetTabBoxes().front(); + } + return pBox; +} + +SwTableBox* SwTable::GetTableBox( sal_uLong nSttIdx ) +{ + // For optimizations, don't always process the entire SortArray. + // Converting text to table, tries certain conditions + // to ask for a table box of a table that is not yet having a format + if(!GetFrameFormat()) + return nullptr; + SwTableBox* pRet = nullptr; + SwNodes& rNds = GetFrameFormat()->GetDoc()->GetNodes(); + sal_uLong nIndex = nSttIdx + 1; + SwContentNode* pCNd = nullptr; + SwTableNode* pTableNd = nullptr; + + while ( nIndex < rNds.Count() ) + { + pTableNd = rNds[ nIndex ]->GetTableNode(); + if ( pTableNd ) + break; + + pCNd = rNds[ nIndex ]->GetContentNode(); + if ( pCNd ) + break; + + ++nIndex; + } + + if ( pCNd || pTableNd ) + { + SwModify* pModify = pCNd; + // #144862# Better handling of table in table + if ( pTableNd && pTableNd->GetTable().GetFrameFormat() ) + pModify = pTableNd->GetTable().GetFrameFormat(); + + SwFrame* pFrame = pModify ? SwIterator<SwFrame,SwModify>(*pModify).First() : nullptr; + while ( pFrame && !pFrame->IsCellFrame() ) + pFrame = pFrame->GetUpper(); + if ( pFrame ) + pRet = const_cast<SwTableBox*>(static_cast<SwCellFrame*>(pFrame)->GetTabBox()); + } + + // In case the layout doesn't exist yet or anything else goes wrong. + if ( !pRet ) + { + for (size_t n = m_TabSortContentBoxes.size(); n; ) + { + if (m_TabSortContentBoxes[ --n ]->GetSttIdx() == nSttIdx) + { + return m_TabSortContentBoxes[ n ]; + } + } + } + return pRet; +} + +bool SwTable::IsTableComplex() const +{ + // Returns true for complex tables, i.e. tables that contain nestings, + // like containing boxes not part of the first line, e.g. results of + // splits/merges which lead to more complex structures. + for (size_t n = 0; n < m_TabSortContentBoxes.size(); ++n) + { + if (m_TabSortContentBoxes[ n ]->GetUpper()->GetUpper()) + { + return true; + } + } + return false; +} + +SwTableLine::SwTableLine( SwTableLineFormat *pFormat, sal_uInt16 nBoxes, + SwTableBox *pUp ) + : SwClient( pFormat ), + m_aBoxes(), + m_pUpper( pUp ) +{ + m_aBoxes.reserve( nBoxes ); +} + +SwTableLine::~SwTableLine() +{ + for (size_t i = 0; i < m_aBoxes.size(); ++i) + { + delete m_aBoxes[i]; + } + // the TabelleLine can be deleted if it's the last client of the FrameFormat + SwModify* pMod = GetFrameFormat(); + pMod->Remove( this ); // remove, + if( !pMod->HasWriterListeners() ) + delete pMod; // and delete +} + +SwFrameFormat* SwTableLine::ClaimFrameFormat() +{ + // This method makes sure that this object is an exclusive SwTableLine client + // of an SwTableLineFormat object + // If other SwTableLine objects currently listen to the same SwTableLineFormat as + // this one, something needs to be done + SwTableLineFormat *pRet = static_cast<SwTableLineFormat*>(GetFrameFormat()); + SwIterator<SwTableLine,SwFormat> aIter( *pRet ); + for( SwTableLine* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if ( pLast != this ) + { + // found another SwTableLine that is a client of the current Format + // create a new Format as a copy and use it for this object + SwTableLineFormat *pNewFormat = pRet->GetDoc()->MakeTableLineFormat(); + *pNewFormat = *pRet; + + // register SwRowFrames that know me as clients at the new Format + SwIterator<SwRowFrame,SwFormat> aFrameIter( *pRet ); + for( SwRowFrame* pFrame = aFrameIter.First(); pFrame; pFrame = aFrameIter.Next() ) + if( pFrame->GetTabLine() == this ) + pFrame->RegisterToFormat( *pNewFormat ); + + // register myself + pNewFormat->Add( this ); + pRet = pNewFormat; + break; + } + } + + return pRet; +} + +void SwTableLine::ChgFrameFormat( SwTableLineFormat *pNewFormat ) +{ + SwFrameFormat *pOld = GetFrameFormat(); + SwIterator<SwRowFrame,SwFormat> aIter( *pOld ); + + // First, re-register the Frames. + for( SwRowFrame* pRow = aIter.First(); pRow; pRow = aIter.Next() ) + { + if( pRow->GetTabLine() == this ) + { + pRow->RegisterToFormat( *pNewFormat ); + + pRow->InvalidateSize(); + pRow->InvalidatePrt_(); + pRow->SetCompletePaint(); + pRow->ReinitializeFrameSizeAttrFlags(); + + // #i35063# + // consider 'split row allowed' attribute + SwTabFrame* pTab = pRow->FindTabFrame(); + bool bInFollowFlowRow = false; + const bool bInFirstNonHeadlineRow = pTab->IsFollow() && + pRow == pTab->GetFirstNonHeadlineRow(); + if ( bInFirstNonHeadlineRow || + !pRow->GetNext() || + ( bInFollowFlowRow = pRow->IsInFollowFlowRow() ) || + nullptr != pRow->IsInSplitTableRow() ) + { + if ( bInFirstNonHeadlineRow || bInFollowFlowRow ) + pTab = pTab->FindMaster(); + + pTab->SetRemoveFollowFlowLinePending( true ); + pTab->InvalidatePos(); + } + } + } + + // Now, re-register self. + pNewFormat->Add( this ); + + if ( !pOld->HasWriterListeners() ) + delete pOld; +} + +SwTwips SwTableLine::GetTableLineHeight( bool& bLayoutAvailable ) const +{ + SwTwips nRet = 0; + bLayoutAvailable = false; + SwIterator<SwRowFrame,SwFormat> aIter( *GetFrameFormat() ); + // A row could appear several times in headers/footers so only one chain of master/follow tables + // will be accepted... + const SwTabFrame* pChain = nullptr; // My chain + for( SwRowFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if( pLast->GetTabLine() == this ) + { + const SwTabFrame* pTab = pLast->FindTabFrame(); + bLayoutAvailable = ( pTab && pTab->IsVertical() ) ? + ( 0 < pTab->getFrameArea().Height() ) : + ( 0 < pTab->getFrameArea().Width() ); + + // The first one defines the chain, if a chain is defined, only members of the chain + // will be added. + if (pTab && (!pChain || pChain->IsAnFollow( pTab ) || pTab->IsAnFollow(pChain))) + { + pChain = pTab; // defines my chain (even it is already) + if( pTab->IsVertical() ) + nRet += pLast->getFrameArea().Width(); + else + nRet += pLast->getFrameArea().Height(); + // Optimization, if there are no master/follows in my chain, nothing more to add + if( !pTab->HasFollow() && !pTab->IsFollow() ) + break; + // This is not an optimization, this is necessary to avoid double additions of + // repeating rows + if( pTab->IsInHeadline(*pLast) ) + break; + } + } + } + return nRet; +} + +SwTableBox::SwTableBox( SwTableBoxFormat* pFormat, sal_uInt16 nLines, SwTableLine *pUp ) + : SwClient(nullptr) + , m_aLines() + , m_pStartNode(nullptr) + , m_pUpper(pUp) + , mnRowSpan(1) + , mbDummyFlag(false) + , mbDirectFormatting(false) +{ + m_aLines.reserve( nLines ); + CheckBoxFormat( pFormat )->Add( this ); +} + +SwTableBox::SwTableBox( SwTableBoxFormat* pFormat, const SwNodeIndex &rIdx, + SwTableLine *pUp ) + : SwClient(nullptr) + , m_aLines() + , m_pUpper(pUp) + , mnRowSpan(1) + , mbDummyFlag(false) + , mbDirectFormatting(false) +{ + CheckBoxFormat( pFormat )->Add( this ); + + m_pStartNode = rIdx.GetNode().GetStartNode(); + + // insert into the table + const SwTableNode* pTableNd = m_pStartNode->FindTableNode(); + assert(pTableNd && "In which table is that box?"); + SwTableSortBoxes& rSrtArr = const_cast<SwTableSortBoxes&>(pTableNd->GetTable(). + GetTabSortBoxes()); + SwTableBox* p = this; // error: &this + rSrtArr.insert( p ); // insert +} + +SwTableBox::SwTableBox( SwTableBoxFormat* pFormat, const SwStartNode& rSttNd, SwTableLine *pUp ) + : SwClient(nullptr) + , m_aLines() + , m_pStartNode(&rSttNd) + , m_pUpper(pUp) + , mnRowSpan(1) + , mbDummyFlag(false) + , mbDirectFormatting(false) +{ + CheckBoxFormat( pFormat )->Add( this ); + + // insert into the table + const SwTableNode* pTableNd = m_pStartNode->FindTableNode(); + OSL_ENSURE( pTableNd, "In which table is the box?" ); + SwTableSortBoxes& rSrtArr = const_cast<SwTableSortBoxes&>(pTableNd->GetTable(). + GetTabSortBoxes()); + SwTableBox* p = this; // error: &this + rSrtArr.insert( p ); // insert +} + +void SwTableBox::RemoveFromTable() +{ + if (m_pStartNode) // box containing contents? + { + // remove from table + const SwTableNode* pTableNd = m_pStartNode->FindTableNode(); + assert(pTableNd && "In which table is that box?"); + SwTableSortBoxes& rSrtArr = const_cast<SwTableSortBoxes&>(pTableNd->GetTable(). + GetTabSortBoxes()); + SwTableBox *p = this; // error: &this + rSrtArr.erase( p ); // remove + m_pStartNode = nullptr; // clear it so this is only run once + } +} + +SwTableBox::~SwTableBox() +{ + if (!GetFrameFormat()->GetDoc()->IsInDtor()) + { + RemoveFromTable(); + } + + // the TabelleBox can be deleted if it's the last client of the FrameFormat + SwModify* pMod = GetFrameFormat(); + pMod->Remove( this ); // remove, + if( !pMod->HasWriterListeners() ) + delete pMod; // and delete +} + +SwTableBoxFormat* SwTableBox::CheckBoxFormat( SwTableBoxFormat* pFormat ) +{ + // We might need to create a new format here, because the box must be + // added to the format solely if pFormat has a value or form. + if( SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_VALUE, false ) || + SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_FORMULA, false ) ) + { + SwTableBox* pOther = SwIterator<SwTableBox,SwFormat>( *pFormat ).First(); + if( pOther ) + { + SwTableBoxFormat* pNewFormat = pFormat->GetDoc()->MakeTableBoxFormat(); + pNewFormat->LockModify(); + *pNewFormat = *pFormat; + + // Remove values and formulas + pNewFormat->ResetFormatAttr( RES_BOXATR_FORMULA, RES_BOXATR_VALUE ); + pNewFormat->UnlockModify(); + + pFormat = pNewFormat; + } + } + return pFormat; +} + +SwFrameFormat* SwTableBox::ClaimFrameFormat() +{ + // This method makes sure that this object is an exclusive SwTableBox client + // of an SwTableBoxFormat object + // If other SwTableBox objects currently listen to the same SwTableBoxFormat as + // this one, something needs to be done + SwTableBoxFormat *pRet = static_cast<SwTableBoxFormat*>(GetFrameFormat()); + SwIterator<SwTableBox,SwFormat> aIter( *pRet ); + for( SwTableBox* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if ( pLast != this ) + { + // Found another SwTableBox object + // create a new Format as a copy and assign me to it + // don't copy values and formulas + SwTableBoxFormat* pNewFormat = pRet->GetDoc()->MakeTableBoxFormat(); + pNewFormat->LockModify(); + *pNewFormat = *pRet; + pNewFormat->ResetFormatAttr( RES_BOXATR_FORMULA, RES_BOXATR_VALUE ); + pNewFormat->UnlockModify(); + + // re-register SwCellFrame objects that know me + SwIterator<SwCellFrame,SwFormat> aFrameIter( *pRet ); + for( SwCellFrame* pCell = aFrameIter.First(); pCell; pCell = aFrameIter.Next() ) + if( pCell->GetTabBox() == this ) + pCell->RegisterToFormat( *pNewFormat ); + + // re-register myself + pNewFormat->Add( this ); + pRet = pNewFormat; + break; + } + } + return pRet; +} + +void SwTableBox::ChgFrameFormat( SwTableBoxFormat* pNewFormat, bool bNeedToReregister ) +{ + SwFrameFormat *pOld = GetFrameFormat(); + SwIterator<SwCellFrame,SwFormat> aIter( *pOld ); + + // tdf#84635 We set bNeedToReregister=false to avoid a quadratic slowdown on loading large tables, + // and since we are creating the table for the first time, no re-registration is necessary. + + // First, re-register the Frames. + if (bNeedToReregister) + for( SwCellFrame* pCell = aIter.First(); pCell; pCell = aIter.Next() ) + { + if( pCell->GetTabBox() == this ) + { + pCell->RegisterToFormat( *pNewFormat ); + pCell->InvalidateSize(); + pCell->InvalidatePrt_(); + pCell->SetCompletePaint(); + pCell->SetDerivedVert( false ); + pCell->CheckDirChange(); + + // #i47489# + // make sure that the row will be formatted, in order + // to have the correct Get(Top|Bottom)MarginForLowers values + // set at the row. + const SwTabFrame* pTab = pCell->FindTabFrame(); + if ( pTab && pTab->IsCollapsingBorders() ) + { + SwFrame* pRow = pCell->GetUpper(); + pRow->InvalidateSize_(); + pRow->InvalidatePrt_(); + } + } + } + + // Now, re-register self. + pNewFormat->Add( this ); + + if( !pOld->HasWriterListeners() ) + delete pOld; +} + +// Return the name of this box. This is determined dynamically +// resulting from the position in the lines/boxes/tables. +void sw_GetTableBoxColStr( sal_uInt16 nCol, OUString& rNm ) +{ + const sal_uInt16 coDiff = 52; // 'A'-'Z' 'a' - 'z' + + do { + const sal_uInt16 nCalc = nCol % coDiff; + if( nCalc >= 26 ) + rNm = OUStringChar( sal_Unicode('a' - 26 + nCalc) ) + rNm; + else + rNm = OUStringChar( sal_Unicode('A' + nCalc) ) + rNm; + + if( 0 == (nCol = nCol - nCalc) ) + break; + nCol /= coDiff; + --nCol; + } while( true ); +} + +Point SwTableBox::GetCoordinates() const +{ + if( !m_pStartNode ) // box without content? + { + // search for the next first box? + return Point( 0, 0 ); + } + + const SwTable& rTable = m_pStartNode->FindTableNode()->GetTable(); + sal_uInt16 nX, nY; + const SwTableBox* pBox = this; + do { + const SwTableLine* pLine = pBox->GetUpper(); + // at the first level? + const SwTableLines* pLines = pLine->GetUpper() + ? &pLine->GetUpper()->GetTabLines() : &rTable.GetTabLines(); + + nY = pLines->GetPos( pLine ) + 1 ; + nX = pBox->GetUpper()->GetBoxPos( pBox ) + 1; + pBox = pLine->GetUpper(); + } while( pBox ); + return Point( nX, nY ); +} + +OUString SwTableBox::GetName() const +{ + if( !m_pStartNode ) // box without content? + { + // search for the next first box? + return OUString(); + } + + const SwTable& rTable = m_pStartNode->FindTableNode()->GetTable(); + sal_uInt16 nPos; + OUString sNm, sTmp; + const SwTableBox* pBox = this; + do { + const SwTableLine* pLine = pBox->GetUpper(); + // at the first level? + const SwTableLines* pLines = pLine->GetUpper() + ? &pLine->GetUpper()->GetTabLines() : &rTable.GetTabLines(); + + nPos = pLines->GetPos( pLine ) + 1; + sTmp = OUString::number( nPos ); + if( !sNm.isEmpty() ) + sNm = sTmp + "." + sNm; + else + sNm = sTmp; + + nPos = pBox->GetUpper()->GetBoxPos( pBox ); + sTmp = OUString::number(nPos + 1); + if( nullptr != ( pBox = pLine->GetUpper()) ) + sNm = sTmp + "." + sNm; + else + sw_GetTableBoxColStr( nPos, sNm ); + + } while( pBox ); + return sNm; +} + +bool SwTableBox::IsInHeadline( const SwTable* pTable ) const +{ + if( !GetUpper() ) // should only happen upon merge. + return false; + + if( !pTable ) + pTable = &m_pStartNode->FindTableNode()->GetTable(); + + const SwTableLine* pLine = GetUpper(); + while( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + // Headerline? + return pTable->GetTabLines()[ 0 ] == pLine; +} + +sal_uLong SwTableBox::GetSttIdx() const +{ + return m_pStartNode ? m_pStartNode->GetIndex() : 0; +} + + // retrieve information from the client +bool SwTable::GetInfo( SfxPoolItem& rInfo ) const +{ + switch( rInfo.Which() ) + { + case RES_AUTOFMT_DOCNODE: + { + const SwTableNode* pNode = GetTableNode(); + if (pNode && &pNode->GetNodes() == static_cast<SwAutoFormatGetDocNode&>(rInfo).pNodes) + { + if (!m_TabSortContentBoxes.empty()) + { + SwNodeIndex aIdx( *m_TabSortContentBoxes[0]->GetSttNd() ); + GetFrameFormat()->GetDoc()->GetNodes().GoNext( &aIdx ); + } + return false; + } + break; + } + case RES_FINDNEARESTNODE: + if( GetFrameFormat() && + GetFrameFormat()->GetFormatAttr( RES_PAGEDESC ).GetPageDesc() && + !m_TabSortContentBoxes.empty() && + m_TabSortContentBoxes[0]->GetSttNd()->GetNodes().IsDocNodes() ) + static_cast<SwFindNearestNode&>(rInfo).CheckNode( * + m_TabSortContentBoxes[0]->GetSttNd()->FindTableNode() ); + break; + + case RES_CONTENT_VISIBLE: + static_cast<SwPtrMsgPoolItem&>(rInfo).pObject = SwIterator<SwFrame,SwFormat>( *GetFrameFormat() ).First(); + return false; + } + return true; +} + +SwTable * SwTable::FindTable( SwFrameFormat const*const pFormat ) +{ + return pFormat + ? SwIterator<SwTable,SwFormat>(*pFormat).First() + : nullptr; +} + +SwTableNode* SwTable::GetTableNode() const +{ + return !GetTabSortBoxes().empty() ? + const_cast<SwTableNode*>(GetTabSortBoxes()[ 0 ]->GetSttNd()->FindTableNode()) : + m_pTableNode; +} + +void SwTable::SetRefObject( SwServerObject* pObj ) +{ + if( m_xRefObj.is() ) + m_xRefObj->Closed(); + + m_xRefObj = pObj; +} + +void SwTable::SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout> const& r) +{ + m_xHTMLLayout = r; +} + +static void ChgTextToNum( SwTableBox& rBox, const OUString& rText, const Color* pCol, + bool bChgAlign ) +{ + sal_uLong nNdPos = rBox.IsValidNumTextNd(); + ChgTextToNum( rBox,rText,pCol,bChgAlign,nNdPos); +} +void ChgTextToNum( SwTableBox& rBox, const OUString& rText, const Color* pCol, + bool bChgAlign,sal_uLong nNdPos ) +{ + + if( ULONG_MAX == nNdPos ) + return; + + SwDoc* pDoc = rBox.GetFrameFormat()->GetDoc(); + SwTextNode* pTNd = pDoc->GetNodes()[ nNdPos ]->GetTextNode(); + const SfxPoolItem* pItem; + + // assign adjustment + if( bChgAlign ) + { + pItem = &pTNd->SwContentNode::GetAttr( RES_PARATR_ADJUST ); + SvxAdjust eAdjust = static_cast<const SvxAdjustItem*>(pItem)->GetAdjust(); + if( SvxAdjust::Left == eAdjust || SvxAdjust::Block == eAdjust ) + { + SvxAdjustItem aAdjust( *static_cast<const SvxAdjustItem*>(pItem) ); + aAdjust.SetAdjust( SvxAdjust::Right ); + pTNd->SetAttr( aAdjust ); + } + } + + // assign color or save "user color" + if( !pTNd->GetpSwAttrSet() || SfxItemState::SET != pTNd->GetpSwAttrSet()-> + GetItemState( RES_CHRATR_COLOR, false, &pItem )) + pItem = nullptr; + + const Color* pOldNumFormatColor = rBox.GetSaveNumFormatColor(); + const Color* pNewUserColor = pItem ? &static_cast<const SvxColorItem*>(pItem)->GetValue() : nullptr; + + if( ( pNewUserColor && pOldNumFormatColor && + *pNewUserColor == *pOldNumFormatColor ) || + ( !pNewUserColor && !pOldNumFormatColor )) + { + // Keep the user color, set updated values, delete old NumFormatColor if needed + if( pCol ) + // if needed, set the color + pTNd->SetAttr( SvxColorItem( *pCol, RES_CHRATR_COLOR )); + else if( pItem ) + { + pNewUserColor = rBox.GetSaveUserColor(); + if( pNewUserColor ) + pTNd->SetAttr( SvxColorItem( *pNewUserColor, RES_CHRATR_COLOR )); + else + pTNd->ResetAttr( RES_CHRATR_COLOR ); + } + } + else + { + // Save user color, set NumFormat color if needed, but never reset the color + rBox.SetSaveUserColor( pNewUserColor ); + + if( pCol ) + // if needed, set the color + pTNd->SetAttr( SvxColorItem( *pCol, RES_CHRATR_COLOR )); + + } + rBox.SetSaveNumFormatColor( pCol ); + + if( pTNd->GetText() != rText ) + { + // Exchange text. Bugfix to keep Tabs (front and back!) and annotations (inword comment anchors) + const OUString& rOrig = pTNd->GetText(); + sal_Int32 n; + + for( n = 0; n < rOrig.getLength() && ('\x9' == rOrig[n] || CH_TXTATR_INWORD == rOrig[n]); ++n ) + ; + for( ; n < rOrig.getLength() && '\x01' == rOrig[n]; ++n ) + ; + SwIndex aIdx( pTNd, n ); + for( n = rOrig.getLength(); n && ('\x9' == rOrig[--n] || CH_TXTATR_INWORD == rOrig[n]); ) + ; + sal_Int32 nEndPos = n; + n -= aIdx.GetIndex() - 1; + + // Reset DontExpand-Flags before exchange, to retrigger expansion + { + SwIndex aResetIdx( aIdx, n ); + pTNd->DontExpandFormat( aResetIdx, false, false ); + } + + if( !pDoc->getIDocumentRedlineAccess().IsIgnoreRedline() && !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + SwPaM aTemp(*pTNd, 0, *pTNd, rOrig.getLength()); + pDoc->getIDocumentRedlineAccess().DeleteRedline(aTemp, true, RedlineType::Any); + } + + // preserve comments inside of the number by deleting number portions starting from the back + sal_Int32 nCommentPos = pTNd->GetText().lastIndexOf( CH_TXTATR_INWORD, nEndPos ); + while( nCommentPos > aIdx.GetIndex() ) + { + pTNd->EraseText( SwIndex(pTNd, nCommentPos+1), nEndPos - nCommentPos, SwInsertFlags::EMPTYEXPAND ); + // find the next non-sequential comment anchor + do + { + nEndPos = nCommentPos; + n = nEndPos - aIdx.GetIndex(); + nCommentPos = pTNd->GetText().lastIndexOf( CH_TXTATR_INWORD, nEndPos ); + --nEndPos; + } + while( nCommentPos > aIdx.GetIndex() && nCommentPos == nEndPos ); + } + + pTNd->EraseText( aIdx, n, SwInsertFlags::EMPTYEXPAND ); + pTNd->InsertText( rText, aIdx, SwInsertFlags::EMPTYEXPAND ); + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + { + SwPaM aTemp(*pTNd, 0, *pTNd, rText.getLength()); + pDoc->getIDocumentRedlineAccess().AppendRedline(new SwRangeRedline(RedlineType::Insert, aTemp), true); + } + } + + // assign vertical orientation + if( bChgAlign && + ( SfxItemState::SET != rBox.GetFrameFormat()->GetItemState( + RES_VERT_ORIENT, true, &pItem ) || + text::VertOrientation::TOP == static_cast<const SwFormatVertOrient*>(pItem)->GetVertOrient() )) + { + rBox.GetFrameFormat()->SetFormatAttr( SwFormatVertOrient( 0, text::VertOrientation::BOTTOM )); + } + +} + +static void ChgNumToText( SwTableBox& rBox, sal_uLong nFormat ) +{ + sal_uLong nNdPos = rBox.IsValidNumTextNd( false ); + if( ULONG_MAX == nNdPos ) + return; + + SwDoc* pDoc = rBox.GetFrameFormat()->GetDoc(); + SwTextNode* pTNd = pDoc->GetNodes()[ nNdPos ]->GetTextNode(); + bool bChgAlign = pDoc->IsInsTableAlignNum(); + const SfxPoolItem* pItem; + + Color* pCol = nullptr; + if( getSwDefaultTextFormat() != nFormat ) + { + // special text format: + OUString sTmp; + const OUString sText( pTNd->GetText() ); + pDoc->GetNumberFormatter()->GetOutputString( sText, nFormat, sTmp, &pCol ); + if( sText != sTmp ) + { + // exchange text + SwIndex aIdx( pTNd, sText.getLength() ); + // Reset DontExpand-Flags before exchange, to retrigger expansion + pTNd->DontExpandFormat( aIdx, false, false ); + aIdx = 0; + pTNd->EraseText( aIdx, SAL_MAX_INT32, SwInsertFlags::EMPTYEXPAND ); + pTNd->InsertText( sTmp, aIdx, SwInsertFlags::EMPTYEXPAND ); + } + } + + const SfxItemSet* pAttrSet = pTNd->GetpSwAttrSet(); + + // assign adjustment + if( bChgAlign && pAttrSet && SfxItemState::SET == pAttrSet->GetItemState( + RES_PARATR_ADJUST, false, &pItem ) && + SvxAdjust::Right == static_cast<const SvxAdjustItem*>(pItem)->GetAdjust() ) + { + pTNd->SetAttr( SvxAdjustItem( SvxAdjust::Left, RES_PARATR_ADJUST ) ); + } + + // assign color or save "user color" + if( !pAttrSet || SfxItemState::SET != pAttrSet-> + GetItemState( RES_CHRATR_COLOR, false, &pItem )) + pItem = nullptr; + + const Color* pOldNumFormatColor = rBox.GetSaveNumFormatColor(); + const Color* pNewUserColor = pItem ? &static_cast<const SvxColorItem*>(pItem)->GetValue() : nullptr; + + if( ( pNewUserColor && pOldNumFormatColor && + *pNewUserColor == *pOldNumFormatColor ) || + ( !pNewUserColor && !pOldNumFormatColor )) + { + // Keep the user color, set updated values, delete old NumFormatColor if needed + if( pCol ) + // if needed, set the color + pTNd->SetAttr( SvxColorItem( *pCol, RES_CHRATR_COLOR )); + else if( pItem ) + { + pNewUserColor = rBox.GetSaveUserColor(); + if( pNewUserColor ) + pTNd->SetAttr( SvxColorItem( *pNewUserColor, RES_CHRATR_COLOR )); + else + pTNd->ResetAttr( RES_CHRATR_COLOR ); + } + } + else + { + // Save user color, set NumFormat color if needed, but never reset the color + rBox.SetSaveUserColor( pNewUserColor ); + + if( pCol ) + // if needed, set the color + pTNd->SetAttr( SvxColorItem( *pCol, RES_CHRATR_COLOR )); + + } + rBox.SetSaveNumFormatColor( pCol ); + + // assign vertical orientation + if( bChgAlign && + SfxItemState::SET == rBox.GetFrameFormat()->GetItemState( + RES_VERT_ORIENT, false, &pItem ) && + text::VertOrientation::BOTTOM == static_cast<const SwFormatVertOrient*>(pItem)->GetVertOrient() ) + { + rBox.GetFrameFormat()->SetFormatAttr( SwFormatVertOrient( 0, text::VertOrientation::TOP )); + } + +} + +// for detection of modifications (mainly TableBoxAttribute) +void SwTableBoxFormat::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + if( !IsModifyLocked() && GetDoc() && !GetDoc()->IsInDtor()) + { + const SwTableBoxNumFormat *pNewFormat = nullptr; + const SwTableBoxFormula *pNewFormula = nullptr; + const SwTableBoxValue *pNewVal = nullptr; + sal_uLong nOldFormat = getSwDefaultTextFormat(); + + switch( pNew ? pNew->Which() : 0 ) + { + case RES_ATTRSET_CHG: + { + const SfxItemSet& rSet = *static_cast<const SwAttrSetChg*>(pNew)->GetChgSet(); + if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMAT, + false, reinterpret_cast<const SfxPoolItem**>(&pNewFormat) ) ) + nOldFormat = static_cast<const SwAttrSetChg*>(pOld)-> + GetChgSet()->Get( RES_BOXATR_FORMAT ).GetValue(); + rSet.GetItemState( RES_BOXATR_FORMULA, false, + reinterpret_cast<const SfxPoolItem**>(&pNewFormula) ); + rSet.GetItemState( RES_BOXATR_VALUE, false, + reinterpret_cast<const SfxPoolItem**>(&pNewVal) ); + break; + } + case RES_BOXATR_FORMAT: + pNewFormat = static_cast<const SwTableBoxNumFormat*>(pNew); + nOldFormat = static_cast<const SwTableBoxNumFormat*>(pOld)->GetValue(); + break; + case RES_BOXATR_FORMULA: + pNewFormula = static_cast<const SwTableBoxFormula*>(pNew); + break; + case RES_BOXATR_VALUE: + pNewVal = static_cast<const SwTableBoxValue*>(pNew); + break; + } + + // something changed and some BoxAttribut remained in the set! + if( pNewFormat || pNewFormula || pNewVal ) + { + GetDoc()->getIDocumentFieldsAccess().SetFieldsDirty(true, nullptr, 0); + + if( SfxItemState::SET == GetItemState( RES_BOXATR_FORMAT, false ) || + SfxItemState::SET == GetItemState( RES_BOXATR_VALUE, false ) || + SfxItemState::SET == GetItemState( RES_BOXATR_FORMULA, false ) ) + { + // fetch the box + SwIterator<SwTableBox,SwFormat> aIter( *this ); + SwTableBox* pBox = aIter.First(); + if( pBox ) + { + OSL_ENSURE( !aIter.Next(), "zero or more than one box at format" ); + + sal_uLong nNewFormat; + if( pNewFormat ) + { + nNewFormat = pNewFormat->GetValue(); + // new formatting + // is it newer or has the current been removed? + if( SfxItemState::SET != GetItemState( RES_BOXATR_VALUE, false )) + pNewFormat = nullptr; + } + else + { + // fetch the current Item + (void)GetItemState(RES_BOXATR_FORMAT, false, reinterpret_cast<const SfxPoolItem**>(&pNewFormat)); + nOldFormat = GetTableBoxNumFormat().GetValue(); + nNewFormat = pNewFormat ? pNewFormat->GetValue() : nOldFormat; + } + + // is it newer or has the current been removed? + if( pNewVal ) + { + if( GetDoc()->GetNumberFormatter()->IsTextFormat(nNewFormat) ) + nOldFormat = 0; + else + { + if( SfxItemState::SET == GetItemState( RES_BOXATR_VALUE, false )) + nOldFormat = getSwDefaultTextFormat(); + else + nNewFormat = getSwDefaultTextFormat(); + } + } + + // Logic: + // Value change: -> "simulate" a format change! + // Format change: + // Text -> !Text or format change: + // - align right for horizontal alignment, if LEFT or JUSTIFIED + // - align bottom for vertical alignment, if TOP is set, or default + // - replace text (color? negative numbers RED?) + // !Text -> Text: + // - align left for horizontal alignment, if RIGHT + // - align top for vertical alignment, if BOTTOM is set + SvNumberFormatter* pNumFormatr = GetDoc()->GetNumberFormatter(); + bool bNewIsTextFormat = pNumFormatr->IsTextFormat( nNewFormat ); + + if( (!bNewIsTextFormat && nOldFormat != nNewFormat) || pNewFormula ) + { + bool bIsNumFormat = false; + OUString aOrigText; + bool bChgText = true; + double fVal = 0; + if( !pNewVal && SfxItemState::SET != GetItemState( + RES_BOXATR_VALUE, false, reinterpret_cast<const SfxPoolItem**>(&pNewVal) )) + { + // so far, no value has been set, so try to evaluate the content + sal_uLong nNdPos = pBox->IsValidNumTextNd(); + if( ULONG_MAX != nNdPos ) + { + sal_uInt32 nTmpFormatIdx = nNewFormat; + OUString aText( GetDoc()->GetNodes()[ nNdPos ] + ->GetTextNode()->GetRedlineText()); + aOrigText = aText; + if( aText.isEmpty() ) + bChgText = false; + else + { + // Keep Tabs + lcl_TabToBlankAtSttEnd( aText ); + + // JP 22.04.98: Bug 49659 - + // Special casing for percent + if( SvNumFormatType::PERCENT == + pNumFormatr->GetType( nNewFormat )) + { + sal_uInt32 nTmpFormat = 0; + if( GetDoc()->IsNumberFormat( + aText, nTmpFormat, fVal )) + { + if( SvNumFormatType::NUMBER == + pNumFormatr->GetType( nTmpFormat )) + aText += "%"; + + bIsNumFormat = GetDoc()->IsNumberFormat( + aText, nTmpFormatIdx, fVal ); + } + } + else + bIsNumFormat = GetDoc()->IsNumberFormat( + aText, nTmpFormatIdx, fVal ); + + if( bIsNumFormat ) + { + // directly assign value - without Modify + bool bIsLockMod = IsModifyLocked(); + LockModify(); + SetFormatAttr( SwTableBoxValue( fVal )); + if( !bIsLockMod ) + UnlockModify(); + } + } + } + } + else + { + fVal = pNewVal->GetValue(); + bIsNumFormat = true; + } + + // format contents with the new value assigned and write to paragraph + Color* pCol = nullptr; + OUString sNewText; + if( DBL_MAX == fVal ) + { + sNewText = SwViewShell::GetShellRes()->aCalc_Error; + } + else + { + if (bIsNumFormat) + pNumFormatr->GetOutputString( fVal, nNewFormat, sNewText, &pCol ); + else + { + // Original text could not be parsed as + // number/date/time/..., so keep the text. +#if 0 + // Actually the text should be formatted + // according to the format, which may include + // additional text from the format, for example + // in {0;-0;"BAD: "@}. But other places when + // entering a new value or changing text or + // changing to a different format of type Text + // don't do this (yet?). + pNumFormatr->GetOutputString( aOrigText, nNewFormat, sNewText, &pCol ); +#else + sNewText = aOrigText; +#endif + } + + if( !bChgText ) + { + sNewText.clear(); + } + } + + // across all boxes + ChgTextToNum( *pBox, sNewText, pCol, + GetDoc()->IsInsTableAlignNum() ); + + } + else if( bNewIsTextFormat && nOldFormat != nNewFormat ) + { + ChgNumToText( *pBox, nNewFormat ); + } + } + } + } + } + // call base class + SwFrameFormat::Modify( pOld, pNew ); +} + +bool SwTableBoxFormat::supportsFullDrawingLayerFillAttributeSet() const +{ + return false; +} + +bool SwTableFormat::supportsFullDrawingLayerFillAttributeSet() const +{ + return false; +} + +bool SwTableLineFormat::supportsFullDrawingLayerFillAttributeSet() const +{ + return false; +} + +bool SwTableBox::HasNumContent( double& rNum, sal_uInt32& rFormatIndex, + bool& rIsEmptyTextNd ) const +{ + bool bRet = false; + sal_uLong nNdPos = IsValidNumTextNd(); + if( ULONG_MAX != nNdPos ) + { + OUString aText( m_pStartNode->GetNodes()[ nNdPos ]->GetTextNode()->GetRedlineText() ); + // Keep Tabs + lcl_TabToBlankAtSttEnd( aText ); + rIsEmptyTextNd = aText.isEmpty(); + SvNumberFormatter* pNumFormatr = GetFrameFormat()->GetDoc()->GetNumberFormatter(); + + const SfxPoolItem* pItem; + if( SfxItemState::SET == GetFrameFormat()->GetItemState( RES_BOXATR_FORMAT, false, &pItem )) + { + rFormatIndex = static_cast<const SwTableBoxNumFormat*>(pItem)->GetValue(); + // Special casing for percent + if( !rIsEmptyTextNd && SvNumFormatType::PERCENT == pNumFormatr->GetType( rFormatIndex )) + { + sal_uInt32 nTmpFormat = 0; + if( GetFrameFormat()->GetDoc()->IsNumberFormat( aText, nTmpFormat, rNum ) && + SvNumFormatType::NUMBER == pNumFormatr->GetType( nTmpFormat )) + aText += "%"; + } + } + else + rFormatIndex = 0; + + bRet = GetFrameFormat()->GetDoc()->IsNumberFormat( aText, rFormatIndex, rNum ); + } + else + rIsEmptyTextNd = false; + return bRet; +} + +bool SwTableBox::IsNumberChanged() const +{ + bool bRet = true; + + if( SfxItemState::SET == GetFrameFormat()->GetItemState( RES_BOXATR_FORMULA, false )) + { + const SwTableBoxNumFormat *pNumFormat; + const SwTableBoxValue *pValue; + + if( SfxItemState::SET != GetFrameFormat()->GetItemState( RES_BOXATR_VALUE, false, + reinterpret_cast<const SfxPoolItem**>(&pValue) )) + pValue = nullptr; + if( SfxItemState::SET != GetFrameFormat()->GetItemState( RES_BOXATR_FORMAT, false, + reinterpret_cast<const SfxPoolItem**>(&pNumFormat) )) + pNumFormat = nullptr; + + sal_uLong nNdPos; + if( pNumFormat && pValue && ULONG_MAX != ( nNdPos = IsValidNumTextNd() ) ) + { + OUString sNewText, sOldText( m_pStartNode->GetNodes()[ nNdPos ]-> + GetTextNode()->GetRedlineText() ); + lcl_DelTabsAtSttEnd( sOldText ); + + Color* pCol = nullptr; + GetFrameFormat()->GetDoc()->GetNumberFormatter()->GetOutputString( + pValue->GetValue(), pNumFormat->GetValue(), sNewText, &pCol ); + + bRet = sNewText != sOldText || + !( ( !pCol && !GetSaveNumFormatColor() ) || + ( pCol && GetSaveNumFormatColor() && + *pCol == *GetSaveNumFormatColor() )); + } + } + return bRet; +} + +sal_uLong SwTableBox::IsValidNumTextNd( bool bCheckAttr ) const +{ + sal_uLong nPos = ULONG_MAX; + if( m_pStartNode ) + { + SwNodeIndex aIdx( *m_pStartNode ); + sal_uLong nIndex = aIdx.GetIndex(); + const sal_uLong nIndexEnd = m_pStartNode->GetNodes()[ nIndex ]->EndOfSectionIndex(); + const SwTextNode *pTextNode = nullptr; + while( ++nIndex < nIndexEnd ) + { + const SwNode* pNode = m_pStartNode->GetNodes()[nIndex]; + if( pNode->IsTableNode() ) + { + pTextNode = nullptr; + break; + } + if( pNode->IsTextNode() ) + { + if( pTextNode ) + { + pTextNode = nullptr; + break; + } + else + { + pTextNode = pNode->GetTextNode(); + nPos = nIndex; + } + } + } + if( pTextNode ) + { + if( bCheckAttr ) + { + const SwpHints* pHts = pTextNode->GetpSwpHints(); + // do some tests if there's only text in the node! + // Flys/fields/... + if( pHts ) + { + sal_Int32 nNextSetField = 0; + for( size_t n = 0; n < pHts->Count(); ++n ) + { + const SwTextAttr* pAttr = pHts->Get(n); + if( RES_TXTATR_NOEND_BEGIN <= pAttr->Which() ) + { + if ( (pAttr->GetStart() == nNextSetField) + && (pAttr->Which() == RES_TXTATR_FIELD)) + { + // #i104949# hideous hack for report builder: + // it inserts hidden variable-set fields at + // the beginning of para in cell, but they + // should not turn cell into text cell + const SwField* pField = pAttr->GetFormatField().GetField(); + if (pField && + (pField->GetTypeId() == SwFieldTypesEnum::Set) && + (0 != (static_cast<SwSetExpField const*> + (pField)->GetSubType() & + nsSwExtendedSubType::SUB_INVISIBLE))) + { + nNextSetField = pAttr->GetStart() + 1; + continue; + } + } + else if( RES_TXTATR_ANNOTATION == pAttr->Which() ) + { + continue; + } + nPos = ULONG_MAX; + break; + } + } + } + } + } + else + nPos = ULONG_MAX; + } + return nPos; +} + +// is this a Formula box or one with numeric content (AutoSum) +sal_uInt16 SwTableBox::IsFormulaOrValueBox() const +{ + sal_uInt16 nWhich = 0; + const SwTextNode* pTNd; + SwFrameFormat* pFormat = GetFrameFormat(); + if( SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_FORMULA, false )) + nWhich = RES_BOXATR_FORMULA; + else if( SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_VALUE, false ) && + !pFormat->GetDoc()->GetNumberFormatter()->IsTextFormat( + pFormat->GetTableBoxNumFormat().GetValue() )) + nWhich = RES_BOXATR_VALUE; + else if( m_pStartNode && m_pStartNode->GetIndex() + 2 == m_pStartNode->EndOfSectionIndex() + && nullptr != ( pTNd = m_pStartNode->GetNodes()[ m_pStartNode->GetIndex() + 1 ] + ->GetTextNode() ) && pTNd->GetText().isEmpty()) + nWhich = USHRT_MAX; + + return nWhich; +} + +void SwTableBox::ActualiseValueBox() +{ + const SfxPoolItem *pFormatItem, *pValItem; + SwFrameFormat* pFormat = GetFrameFormat(); + if( SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_FORMAT, true, &pFormatItem ) + && SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_VALUE, true, &pValItem )) + { + const sal_uLong nFormatId = static_cast<const SwTableBoxNumFormat*>(pFormatItem)->GetValue(); + sal_uLong nNdPos = ULONG_MAX; + SvNumberFormatter* pNumFormatr = pFormat->GetDoc()->GetNumberFormatter(); + + if( !pNumFormatr->IsTextFormat( nFormatId ) && + ULONG_MAX != (nNdPos = IsValidNumTextNd()) ) + { + double fVal = static_cast<const SwTableBoxValue*>(pValItem)->GetValue(); + Color* pCol = nullptr; + OUString sNewText; + pNumFormatr->GetOutputString( fVal, nFormatId, sNewText, &pCol ); + + const OUString& rText = m_pStartNode->GetNodes()[ nNdPos ]->GetTextNode()->GetText(); + if( rText != sNewText ) + ChgTextToNum( *this, sNewText, pCol, false ,nNdPos); + } + } +} + +struct SwTableCellInfo::Impl +{ + const SwTable * m_pTable; + const SwCellFrame * m_pCellFrame; + const SwTabFrame * m_pTabFrame; + typedef o3tl::sorted_vector<const SwTableBox *> TableBoxes_t; + TableBoxes_t m_HandledTableBoxes; + +public: + Impl() + : m_pTable(nullptr), m_pCellFrame(nullptr), m_pTabFrame(nullptr) + { + } + + void setTable(const SwTable * pTable) + { + m_pTable = pTable; + SwFrameFormat * pFrameFormat = m_pTable->GetFrameFormat(); + m_pTabFrame = SwIterator<SwTabFrame,SwFormat>(*pFrameFormat).First(); + if (m_pTabFrame && m_pTabFrame->IsFollow()) + m_pTabFrame = m_pTabFrame->FindMaster(true); + } + + const SwCellFrame * getCellFrame() const { return m_pCellFrame; } + + const SwFrame * getNextFrameInTable(const SwFrame * pFrame); + const SwCellFrame * getNextCellFrame(const SwFrame * pFrame); + const SwCellFrame * getNextTableBoxsCellFrame(const SwFrame * pFrame); + bool getNext(); +}; + +const SwFrame * SwTableCellInfo::Impl::getNextFrameInTable(const SwFrame * pFrame) +{ + const SwFrame * pResult = nullptr; + + if (((! pFrame->IsTabFrame()) || pFrame == m_pTabFrame) && pFrame->GetLower()) + pResult = pFrame->GetLower(); + else if (pFrame->GetNext()) + pResult = pFrame->GetNext(); + else + { + while (pFrame->GetUpper() != nullptr) + { + pFrame = pFrame->GetUpper(); + + if (pFrame->IsTabFrame()) + { + m_pTabFrame = static_cast<const SwTabFrame *>(pFrame)->GetFollow(); + pResult = m_pTabFrame; + break; + } + else if (pFrame->GetNext()) + { + pResult = pFrame->GetNext(); + break; + } + } + } + + return pResult; +} + +const SwCellFrame * SwTableCellInfo::Impl::getNextCellFrame(const SwFrame * pFrame) +{ + const SwCellFrame * pResult = nullptr; + + while ((pFrame = getNextFrameInTable(pFrame)) != nullptr) + { + if (pFrame->IsCellFrame()) + { + pResult = static_cast<const SwCellFrame *>(pFrame); + break; + } + } + + return pResult; +} + +const SwCellFrame * SwTableCellInfo::Impl::getNextTableBoxsCellFrame(const SwFrame * pFrame) +{ + const SwCellFrame * pResult = nullptr; + + while ((pFrame = getNextCellFrame(pFrame)) != nullptr) + { + const SwCellFrame * pCellFrame = static_cast<const SwCellFrame *>(pFrame); + const SwTableBox * pTabBox = pCellFrame->GetTabBox(); + auto aIt = m_HandledTableBoxes.insert(pTabBox); + if (aIt.second) + { + pResult = pCellFrame; + break; + } + } + + return pResult; +} + +const SwCellFrame * SwTableCellInfo::getCellFrame() const +{ + return m_pImpl->getCellFrame(); +} + +bool SwTableCellInfo::Impl::getNext() +{ + if (m_pCellFrame == nullptr) + { + if (m_pTabFrame != nullptr) + m_pCellFrame = Impl::getNextTableBoxsCellFrame(m_pTabFrame); + } + else + m_pCellFrame = Impl::getNextTableBoxsCellFrame(m_pCellFrame); + + return m_pCellFrame != nullptr; +} + +SwTableCellInfo::SwTableCellInfo(const SwTable * pTable) + : m_pImpl(std::make_unique<Impl>()) +{ + m_pImpl->setTable(pTable); +} + +SwTableCellInfo::~SwTableCellInfo() +{ +} + +bool SwTableCellInfo::getNext() +{ + return m_pImpl->getNext(); +} + +SwRect SwTableCellInfo::getRect() const +{ + SwRect aRet; + + if (getCellFrame() != nullptr) + aRet = getCellFrame()->getFrameArea(); + + return aRet; +} + +const SwTableBox * SwTableCellInfo::getTableBox() const +{ + const SwTableBox * pRet = nullptr; + + if (getCellFrame() != nullptr) + pRet = getCellFrame()->GetTabBox(); + + return pRet; +} + +void SwTable::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add( this ); +} + +bool SwTable::HasLayout() const +{ + const SwFrameFormat* pFrameFormat = GetFrameFormat(); + //a table in a clipboard document doesn't have any layout information + return pFrameFormat && SwIterator<SwTabFrame,SwFormat>(*pFrameFormat).First(); +} + +void SwTableLine::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add( this ); +} + +void SwTableBox::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add( this ); +} + +// free's any remaining child objects +SwTableLines::~SwTableLines() +{ + for ( const_iterator it = begin(); it != end(); ++it ) + delete *it; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |