/* -*- 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 .
*/
/** 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
*/
/** 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(..)
*/
/** 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
tools::Long nNew = 0; // will be the right border of the current box
tools::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)
/** 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( tools::Long nLeft, const SwTableLine* pLine )
{ if( !pLine ) return nullptr;
tools::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
*/
staticvoid lcl_ChangeRowSpan( const SwTable& rTable, const tools::Long nDiff,
sal_uInt16 nRowIdx, constbool 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.
tools::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 )
{
sal_Int32 nRowSpan = pLine->GetTabBoxes()[nCurrBox]->getRowSpan();
sal_Int32 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
*/
/** lcl_InsertPosition(..) evaluates the insert positions in every table line, when a selection of cells is given and returns the average cell widths
*/
CHECK_TABLE( *this )
tools::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 ) returnfalse;
nAddWidth = (nAddWidth * nTableWidth) / nResultingWidth;
nNewBoxWidth = tools::Long( nAddWidth / nCnt ); // Rounding
nAddWidth = nNewBoxWidth * nCnt; // Rounding if( !nAddWidth || nAddWidth >= nTableWidth ) returnfalse;
AdjustWidths( static_cast< tools::Long >(nTableWidth), static_cast< tools::Long >(nTableWidth - nAddWidth) );
}
/** 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::optional< SwBoxSelection > pSel( CollectBoxSelection( rPam ) ); if (!pSel || pSel->isEmpty()) returnfalse; // 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()) returnfalse;
SwTableBox *pMergeBox = rFirstBoxes[0]; // the master cell box if( !pMergeBox ) returnfalse;
(*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& rDoc = 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
sal_Int32 nRowSpan = static_cast<tools::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 ) )
{
SwNode& rInsPosNd = aInsPos.GetNode();
SwPaM aPam( aInsPos );
aPam.GetPoint()->Assign( *pBox->GetSttNd()->EndOfSectionNode(), SwNodeOffset(-1) );
SwContentNode* pCNd = aPam.GetPointContentNode(); if( pCNd )
aPam.GetPoint()->SetContent( pCNd->Len() );
SwNodeIndex aSttNdIdx( *pBox->GetSttNd(), 1 ); boolconst bUndo = rDoc.GetIDocumentUndoRedo().DoesUndo(); if( pUndo )
{
rDoc.GetIDocumentUndoRedo().DoUndo(false);
}
rDoc.getIDocumentContentOperations().AppendTextNode( *aPam.GetPoint() ); if( pUndo )
{
rDoc.GetIDocumentUndoRedo().DoUndo(bUndo);
}
SwNodeRange aRg( aSttNdIdx.GetNode(), aPam.GetPoint()->GetNode() ); if( pUndo )
pUndo->MoveBoxContent( rDoc, aRg, rInsPosNd ); else
{
rDoc.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()->Adjust(SwNodeOffset(+1));
SwTextNode* pNd = aPam.GetPointNode().GetTextNode(); while( pNd )
{
pNd->SetCountedInList( false );
/** SwTable::InsertSpannedRow(..) inserts "superfluous" rows, i.e. rows containing overlapped cells only. This is a preparation for an upcoming split.
*/
/* * 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 )
*/ staticvoid 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
} elseif( 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;
}
// 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];
assert(pBox && "Missing Table Box");
sal_Int32 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 ) return;
/** This is kind of similar to InsertSpannedRow()/InsertRow() but that one would recursively copy subtables, which would kind of defeat the purpose; this function directly moves the subtable rows's cells into the newly created rows. For the non-subtable boxes, covered-cells are created.
Outer row heights are adjusted to match the inner row heights, and the last row's height is tweaked to ensure the sum of the heights is at least the original outer row's minimal height.
Inner row backgrounds are copied to its cells, if they lack a background.
This currently can't handle more than 1 subtable in a row; the inner rows of all subtables would need to be sorted by their height to create the correct outer row structure, which is tricky and probably requires a layout for the typical variable-height case.
*/ void SwTable::ConvertSubtableBox(sal_uInt16 const nRow, sal_uInt16 const nBox)
{
SwDoc& rDoc(GetFrameFormat()->GetDoc());
SwTableLine *const pSourceLine(GetTabLines()[nRow]);
SwTableBox *const pSubTableBox(pSourceLine->GetTabBoxes()[nBox]);
assert(!pSubTableBox->GetTabLines().empty()); // are relative (%) heights possible? apparently not
SwFormatFrameSize const outerSize(pSourceLine->GetFrameFormat()->GetFrameSize()); if (outerSize.GetHeightSizeType() != SwFrameSize::Variable)
{ // tdf#145871 clear fixed size in first row
pSourceLine->ClaimFrameFormat();
pSourceLine->GetFrameFormat()->ResetFormatAttr(RES_FRM_SIZE);
}
tools::Long minHeights(0);
{
SwFrameFormat const& rSubLineFormat(*pSubTableBox->GetTabLines()[0]->GetFrameFormat());
SwFormatFrameSize const* pSize = rSubLineFormat.GetItemIfSet(RES_FRM_SIZE); if (pSize)
{ // for first row, apply height from inner row to outer row. // in case the existing outer row height was larger than the entire // subtable, the last inserted row needs to be tweaked (below)
pSourceLine->GetFrameFormat()->SetFormatAttr(*pSize); if (pSize->GetHeightSizeType() != SwFrameSize::Variable)
{
minHeights += pSize->GetHeight();
}
}
} for (size_t i = 1; i < pSubTableBox->GetTabLines().size(); ++i)
{
SwTableLine *const pSubLine(pSubTableBox->GetTabLines()[i]);
SwTableLine *const pNewLine = new SwTableLine(
pSourceLine->GetFrameFormat(),
pSourceLine->GetTabBoxes().size() - 1 + pSubLine->GetTabBoxes().size(),
nullptr);
SwFrameFormat const& rSubLineFormat(*pSubLine->GetFrameFormat());
SwFormatFrameSize const* pSize = rSubLineFormat.GetItemIfSet(RES_FRM_SIZE); if (pSize)
{ // for rows 2..N, copy inner row height to outer row
pNewLine->ClaimFrameFormat();
pNewLine->GetFrameFormat()->SetFormatAttr(*pSize); if (pSize->GetHeightSizeType() != SwFrameSize::Variable)
{
minHeights += pSize->GetHeight();
}
} // ensure the sum of the lines is at least as high as the outer line was if (i == pSubTableBox->GetTabLines().size() - 1
&& outerSize.GetHeightSizeType() != SwFrameSize::Variable
&& minHeights < outerSize.GetHeight())
{
SwFormatFrameSize lastSize(pNewLine->GetFrameFormat()->GetFrameSize());
lastSize.SetHeight(lastSize.GetHeight() + outerSize.GetHeight() - minHeights); if (lastSize.GetHeightSizeType() == SwFrameSize::Variable)
{
lastSize.SetHeightSizeType(SwFrameSize::Minimum);
}
pNewLine->ClaimFrameFormat();
pNewLine->GetFrameFormat()->SetFormatAttr(lastSize);
}
SfxPoolItem const* pRowBrush(nullptr);
(void)rSubLineFormat.GetItemState(RES_BACKGROUND, true, &pRowBrush);
GetTabLines().insert(GetTabLines().begin() + nRow + i, pNewLine); for (size_t j = 0; j < pSourceLine->GetTabBoxes().size(); ++j)
{ if (j == nBox)
{ for (size_t k = 0; k < pSubLine->GetTabBoxes().size(); ++k)
{ // move box k to new outer row
SwTableBox *const pSourceBox(pSubLine->GetTabBoxes()[k]);
assert(pSourceBox->getRowSpan() == 1); // import filter (xmltbli.cxx) converts all box widths to absolute
assert(pSourceBox->GetFrameFormat()->GetFrameSize().GetWidthPercent() == 0);
::InsTableBox(rDoc, GetTableNode(), pNewLine,
pSourceBox->GetFrameFormat(),
pSourceBox, j+k, 1); // insert dummy text node...
rDoc.GetNodes().MakeTextNode(
SwNodeIndex(*pSourceBox->GetSttNd(), +1).GetNode(),
rDoc.GetDfltTextFormatColl());
SwNodeRange content(*pSourceBox->GetSttNd(), SwNodeOffset(+2),
*pSourceBox->GetSttNd()->EndOfSectionNode());
SwTableBox *const pNewBox(pNewLine->GetTabBoxes()[j+k]);
SwNodeIndex insPos(*pNewBox->GetSttNd(), 1); // MoveNodes would delete the box SwStartNode/SwEndNode // without the dummy node #if 0
rDoc.GetNodes().MoveNodes(content, rDoc.GetNodes(), insPos, false); #else
rDoc.getIDocumentContentOperations().MoveNodeRange(content, insPos.GetNode(), SwMoveFlags::NO_DELFRMS|SwMoveFlags::REDLINES); #endif // delete the empty node that was bundled in the new box
rDoc.GetNodes().Delete(insPos); if (pRowBrush)
{ if (pNewBox->GetFrameFormat()->GetItemState(RES_BACKGROUND, true) != SfxItemState::SET)
{ // set inner row background on inner cell
pNewBox->ClaimFrameFormat();
pNewBox->GetFrameFormat()->SetFormatAttr(*pRowBrush);
}
} // assume that the borders can be left as they are, because // lines don't have borders, only boxes do
}
} else
{ // insert covered cell for box j
SwTableBox *const pSourceBox(pSourceLine->GetTabBoxes()[j]);
assert(pSourceBox->GetTabLines().empty()); // checked for that
sal_uInt16 const nInsPos(j < nBox ? j : j + pSubLine->GetTabBoxes().size() - 1);
::InsTableBox(rDoc, GetTableNode(), pNewLine,
pSourceBox->GetFrameFormat(),
pSourceBox, nInsPos, 1); // adjust row span: // N rows in subtable, N-1 rows inserted: // -1 -> -N ; -(N-1) ... -1 // -2 -> -(N+1) ; -N .. -2 // 1 -> N ; -(N-1) .. -1 // 2 -> N+1 ; -N .. -2
sal_Int32 newSourceRowSpan(pSourceBox->getRowSpan());
sal_Int32 newBoxRowSpan; if (newSourceRowSpan < 0)
{
newSourceRowSpan -= pSubTableBox->GetTabLines().size() - 1;
newBoxRowSpan = newSourceRowSpan + i;
} else
{
newSourceRowSpan += pSubTableBox->GetTabLines().size() - 1;
newBoxRowSpan = -(newSourceRowSpan - sal::static_int_cast<tools::Long>(i));
}
pNewLine->GetTabBoxes()[nInsPos]->setRowSpan(newBoxRowSpan); if (i == pSubTableBox->GetTabLines().size() - 1)
{ // only last iteration
pSourceBox->setRowSpan(newSourceRowSpan);
}
}
}
} // delete inner rows 2..N while (1 < pSubTableBox->GetTabLines().size())
{ // careful: the last box deletes pSubLine!
SwTableLine *const pSubLine(pSubTableBox->GetTabLines()[1]); for (size_t j = pSubLine->GetTabBoxes().size(); 0 < j; --j)
{
SwTableBox *const pBox(pSubLine->GetTabBoxes()[0]);
DeleteBox_(*this, pBox, nullptr, false, false, nullptr);
}
} // fix row spans in lines preceding nRow
lcl_ChangeRowSpan(*this, pSubTableBox->GetTabLines().size() - 1, nRow - 1, false); // note: the first line of the inner table remains; caller will call // GCLines() to remove it
}
bool SwTable::CanConvertSubtables() const
{ for (SwTableLine const*const pLine : GetTabLines())
{ bool haveSubtable(false); for (SwTableBox const*const pBox : pLine->GetTabBoxes())
{ if (pBox->IsFormulaOrValueBox() == RES_BOXATR_FORMULA)
{ returnfalse; // no table box formulas yet
} if (!pBox->GetTabLines().empty())
{ if (haveSubtable)
{ // can't handle 2 subtable in a row yet returnfalse;
}
haveSubtable = true; bool haveNonFixedInnerLine(false); for (SwTableLine const*const pInnerLine : pBox->GetTabLines())
{ // bitmap row background will look different
SwFrameFormat const& rRowFormat(*pInnerLine->GetFrameFormat());
std::unique_ptr<SvxBrushItem> pBrush(rRowFormat.makeBackgroundBrushItem());
assert(pBrush); if (pBrush->GetGraphicObject() != nullptr)
{ /* TODO: all cells could override this? for (SwTableBox & rInnerBox : rInnerLine.GetTabBoxes())
*/ if (1 < pInnerLine->GetTabBoxes().size()) // except if only 1 cell?
{ returnfalse;
}
} if (SwFormatFrameSize const* pSize = rRowFormat.GetItemIfSet(RES_FRM_SIZE))
{ if (pSize->GetHeightSizeType() != SwFrameSize::Fixed)
{
haveNonFixedInnerLine = true;
}
} else
{
haveNonFixedInnerLine = true; // default
} for (SwTableBox const*const pInnerBox : pInnerLine->GetTabBoxes())
{ if (!pInnerBox->GetTabLines().empty())
{ returnfalse; // nested subtable :(
}
}
} if (haveNonFixedInnerLine)
{ if (SwFormatFrameSize const* pSize = pLine->GetFrameFormat()->GetItemIfSet(RES_FRM_SIZE))
{ if (pSize->GetHeightSizeType() != SwFrameSize::Variable)
{ // not possible to distribute fixed outer row height on rows without layout returnfalse;
}
}
}
}
}
} // note: fields that refer to table cells may be *outside* the table, // so the entire document needs to be imported before checking here // (same for table box formulas and charts)
SwDoc& rDoc(GetFrameFormat()->GetDoc());
SwFieldType const*const pTableFields(
rDoc.getIDocumentFieldsAccess().GetFieldType(SwFieldIds::Table, u""_ustr, false));
std::vector<SwFormatField*> vFields;
pTableFields->GatherFields(vFields); if (!vFields.empty())
{ returnfalse; // no formulas in fields yet
}
std::vector<SwTableBoxFormula*> aTableBoxFormulas;
SwTable::GatherFormulas(rDoc, aTableBoxFormulas); if (!aTableBoxFormulas.empty())
{ returnfalse; // no table box formulas yet
}
UIName const tableName(GetFrameFormat()->GetName());
SwNodeIndex temp(*rDoc.GetNodes().GetEndOfAutotext().StartOfSectionNode(), +1); while (SwStartNode const*const pStartNode = temp.GetNode().GetStartNode())
{
++temp;
SwOLENode const*const pOLENode(temp.GetNode().GetOLENode()); if (pOLENode && tableName == pOLENode->GetChartTableName())
{ // there are charts that refer to this table // presumably such charts would need to be adapted somehow? returnfalse;
}
temp.Assign(*pStartNode->EndOfSectionNode(), +1);
} returntrue;
}
void SwTable::ConvertSubtables()
{
FndBox_ all(nullptr, nullptr);
all.DelFrames(*this); // tdf#151375 avoid UAF by frames on deleted cells for (size_t i = 0; i < GetTabLines().size(); ++i)
{
SwTableLine *const pLine(GetTabLines()[i]); for (size_t j = 0; j < pLine->GetTabBoxes().size(); ++j)
{
SwTableBox *const pBox(pLine->GetTabBoxes()[j]);
SwTableLines & rInnerLines(pBox->GetTabLines()); if (!rInnerLines.empty())
{
ConvertSubtableBox(i, j);
}
}
}
GCLines();
m_bNewModel = true;
all.MakeFrames(*this); #if 0 // note: outline nodes (and ordinary lists) are sorted by MoveNodes() itself // (this could change order inside table of contents, but that's a // really esoteric use-case) // nodes were moved - sort marks, redlines, footnotes
SwDoc *const pDoc(GetFrameFormat()->GetDoc());
rDoc.getIDocumentMarkAccess()->assureSortedMarkContainers();
rDoc.getIDocumentRedlineAccess().GetRedlineTable().Resort();
rDoc.GetFootnoteIdxs().UpdateAllFootnote(); #endif // assume that there aren't any node indexes to the deleted box start/end nodes
CHECK_TABLE( *this )
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.