diff options
Diffstat (limited to 'sw/source/core/docnode/ndtbl.cxx')
-rw-r--r-- | sw/source/core/docnode/ndtbl.cxx | 4684 |
1 files changed, 4684 insertions, 0 deletions
diff --git a/sw/source/core/docnode/ndtbl.cxx b/sw/source/core/docnode/ndtbl.cxx new file mode 100644 index 000000000..e76a0bbf1 --- /dev/null +++ b/sw/source/core/docnode/ndtbl.cxx @@ -0,0 +1,4684 @@ +/* -*- 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 <libxml/xmlwriter.h> + +#include <config_wasm_strip.h> + +#include <memory> +#include <fesh.hxx> +#include <hintids.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/protitem.hxx> +#include <editeng/boxitem.hxx> +#include <svl/stritem.hxx> +#include <editeng/shaditem.hxx> +#include <fmtfsize.hxx> +#include <fmtornt.hxx> +#include <fmtfordr.hxx> +#include <fmtpdsc.hxx> +#include <fmtanchr.hxx> +#include <fmtlsplt.hxx> +#include <frmatr.hxx> +#include <cellfrm.hxx> +#include <pagefrm.hxx> +#include <tabcol.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <UndoManager.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentChartDataProviderAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentState.hxx> +#include <cntfrm.hxx> +#include <pam.hxx> +#include <swcrsr.hxx> +#include <swtable.hxx> +#include <swundo.hxx> +#include <tblsel.hxx> +#include <poolfmt.hxx> +#include <tabfrm.hxx> +#include <UndoCore.hxx> +#include <UndoRedline.hxx> +#include <UndoDelete.hxx> +#include <UndoNumbering.hxx> +#include <UndoTable.hxx> +#include <hints.hxx> +#include <tblafmt.hxx> +#include <frminf.hxx> +#include <cellatr.hxx> +#include <swtblfmt.hxx> +#include <swddetbl.hxx> +#include <mvsave.hxx> +#include <docary.hxx> +#include <redline.hxx> +#include <rolbck.hxx> +#include <tblrwcl.hxx> +#include <editsh.hxx> +#include <txtfrm.hxx> +#include <section.hxx> +#include <frmtool.hxx> +#include <node2lay.hxx> +#include <strings.hrc> +#include <docsh.hxx> +#include <unochart.hxx> +#include <node.hxx> +#include <ndtxt.hxx> +#include <cstdlib> +#include <map> +#include <algorithm> +#include <rootfrm.hxx> +#include <fldupde.hxx> +#include <calbck.hxx> +#include <fntcache.hxx> +#include <frameformats.hxx> +#include <o3tl/numeric.hxx> +#include <o3tl/string_view.hxx> +#include <svl/numformat.hxx> +#include <tools/datetimeutils.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#ifdef DBG_UTIL +#define CHECK_TABLE(t) (t).CheckConsistency(); +#else +#define CHECK_TABLE(t) +#endif + +using ::editeng::SvxBorderLine; +using namespace ::com::sun::star; + +const sal_Unicode T2T_PARA = 0x0a; + +static void lcl_SetDfltBoxAttr( SwFrameFormat& rFormat, sal_uInt8 nId ) +{ + bool bTop = false, bBottom = false, bLeft = false, bRight = false; + switch ( nId ) + { + case 0: bTop = bBottom = bLeft = true; break; + case 1: bTop = bBottom = bLeft = bRight = true; break; + case 2: bBottom = bLeft = true; break; + case 3: bBottom = bLeft = bRight = true; break; + } + + const bool bHTML = rFormat.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE); + Color aCol( bHTML ? COL_GRAY : COL_BLACK ); + // Default border in Writer: 0.5pt (matching Word) + SvxBorderLine aLine( &aCol, SvxBorderLineWidth::VeryThin ); + if ( bHTML ) + { + aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE); + } + SvxBoxItem aBox(RES_BOX); + aBox.SetAllDistances(55); + if ( bTop ) + aBox.SetLine( &aLine, SvxBoxItemLine::TOP ); + if ( bBottom ) + aBox.SetLine( &aLine, SvxBoxItemLine::BOTTOM ); + if ( bLeft ) + aBox.SetLine( &aLine, SvxBoxItemLine::LEFT ); + if ( bRight ) + aBox.SetLine( &aLine, SvxBoxItemLine::RIGHT ); + rFormat.SetFormatAttr( aBox ); +} + +typedef std::map<SwFrameFormat *, SwTableBoxFormat *> DfltBoxAttrMap_t; +typedef std::vector<DfltBoxAttrMap_t *> DfltBoxAttrList_t; + +static void +lcl_SetDfltBoxAttr(SwTableBox& rBox, DfltBoxAttrList_t & rBoxFormatArr, + sal_uInt8 const nId, SwTableAutoFormat const*const pAutoFormat = nullptr) +{ + DfltBoxAttrMap_t * pMap = rBoxFormatArr[ nId ]; + if (!pMap) + { + pMap = new DfltBoxAttrMap_t; + rBoxFormatArr[ nId ] = pMap; + } + + SwTableBoxFormat* pNewTableBoxFormat = nullptr; + SwFrameFormat* pBoxFrameFormat = rBox.GetFrameFormat(); + DfltBoxAttrMap_t::iterator const iter(pMap->find(pBoxFrameFormat)); + if (pMap->end() != iter) + { + pNewTableBoxFormat = iter->second; + } + else + { + SwDoc* pDoc = pBoxFrameFormat->GetDoc(); + // format does not exist, so create it + pNewTableBoxFormat = pDoc->MakeTableBoxFormat(); + pNewTableBoxFormat->SetFormatAttr( pBoxFrameFormat->GetAttrSet().Get( RES_FRM_SIZE ) ); + + if( pAutoFormat ) + pAutoFormat->UpdateToSet( nId, false, false, + const_cast<SfxItemSet&>(static_cast<SfxItemSet const &>(pNewTableBoxFormat->GetAttrSet())), + SwTableAutoFormatUpdateFlags::Box, + pDoc->GetNumberFormatter() ); + else + ::lcl_SetDfltBoxAttr( *pNewTableBoxFormat, nId ); + + (*pMap)[pBoxFrameFormat] = pNewTableBoxFormat; + } + rBox.ChgFrameFormat( pNewTableBoxFormat ); +} + +static SwTableBoxFormat *lcl_CreateDfltBoxFormat( SwDoc &rDoc, std::vector<SwTableBoxFormat*> &rBoxFormatArr, + sal_uInt16 nCols, sal_uInt8 nId ) +{ + if ( !rBoxFormatArr[nId] ) + { + SwTableBoxFormat* pBoxFormat = rDoc.MakeTableBoxFormat(); + if( USHRT_MAX != nCols ) + pBoxFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, + USHRT_MAX / nCols, 0 )); + ::lcl_SetDfltBoxAttr( *pBoxFormat, nId ); + rBoxFormatArr[ nId ] = pBoxFormat; + } + return rBoxFormatArr[nId]; +} + +static SwTableBoxFormat *lcl_CreateAFormatBoxFormat( SwDoc &rDoc, std::vector<SwTableBoxFormat*> &rBoxFormatArr, + const SwTableAutoFormat& rAutoFormat, + const sal_uInt16 nRows, const sal_uInt16 nCols, sal_uInt8 nId ) +{ + if( !rBoxFormatArr[nId] ) + { + SwTableBoxFormat* pBoxFormat = rDoc.MakeTableBoxFormat(); + rAutoFormat.UpdateToSet( nId, nRows==1, nCols==1, + const_cast<SfxItemSet&>(static_cast<SfxItemSet const &>(pBoxFormat->GetAttrSet())), + SwTableAutoFormatUpdateFlags::Box, + rDoc.GetNumberFormatter( ) ); + if( USHRT_MAX != nCols ) + pBoxFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, + USHRT_MAX / nCols, 0 )); + rBoxFormatArr[ nId ] = pBoxFormat; + } + return rBoxFormatArr[nId]; +} + +SwTableNode* SwDoc::IsIdxInTable(const SwNodeIndex& rIdx) +{ + SwTableNode* pTableNd = nullptr; + SwNodeOffset nIndex = rIdx.GetIndex(); + do { + SwNode* pNd = GetNodes()[ nIndex ]->StartOfSectionNode(); + pTableNd = pNd->GetTableNode(); + if( nullptr != pTableNd ) + break; + + nIndex = pNd->GetIndex(); + } while ( nIndex ); + return pTableNd; +} + +/** + * Insert a new Box before the InsPos + */ +bool SwNodes::InsBoxen( SwTableNode* pTableNd, + SwTableLine* pLine, + SwTableBoxFormat* pBoxFormat, + SwTextFormatColl* pTextColl, + const SfxItemSet* pAutoAttr, + sal_uInt16 nInsPos, + sal_uInt16 nCnt ) +{ + if( !nCnt ) + return false; + OSL_ENSURE( pLine, "No valid Line" ); + + // Move Index after the Line's last Box + SwNodeOffset nIdxPos(0); + SwTableBox *pPrvBox = nullptr, *pNxtBox = nullptr; + if( !pLine->GetTabBoxes().empty() ) + { + if( nInsPos < pLine->GetTabBoxes().size() ) + { + pPrvBox = pLine->FindPreviousBox( pTableNd->GetTable(), + pLine->GetTabBoxes()[ nInsPos ] ); + if( nullptr == pPrvBox ) + pPrvBox = pLine->FindPreviousBox( pTableNd->GetTable() ); + } + else + { + pNxtBox = pLine->FindNextBox( pTableNd->GetTable(), + pLine->GetTabBoxes().back() ); + if( nullptr == pNxtBox ) + pNxtBox = pLine->FindNextBox( pTableNd->GetTable() ); + } + } + else + { + pNxtBox = pLine->FindNextBox( pTableNd->GetTable() ); + if( nullptr == pNxtBox ) + pPrvBox = pLine->FindPreviousBox( pTableNd->GetTable() ); + } + + if( !pPrvBox && !pNxtBox ) + { + bool bSetIdxPos = true; + if( !pTableNd->GetTable().GetTabLines().empty() && !nInsPos ) + { + const SwTableLine* pTableLn = pLine; + while( pTableLn->GetUpper() ) + pTableLn = pTableLn->GetUpper()->GetUpper(); + + if( pTableNd->GetTable().GetTabLines()[ 0 ] == pTableLn ) + { + // Before the Table's first Box + while( !( pNxtBox = pLine->GetTabBoxes()[0])->GetTabLines().empty() ) + pLine = pNxtBox->GetTabLines()[0]; + nIdxPos = pNxtBox->GetSttIdx(); + bSetIdxPos = false; + } + } + if( bSetIdxPos ) + // Tables without content or at the end; move before the End + nIdxPos = pTableNd->EndOfSectionIndex(); + } + else if( pNxtBox ) // There is a successor + nIdxPos = pNxtBox->GetSttIdx(); + else // There is a predecessor + nIdxPos = pPrvBox->GetSttNd()->EndOfSectionIndex() + 1; + + SwNodeIndex aEndIdx( *this, nIdxPos ); + for( sal_uInt16 n = 0; n < nCnt; ++n ) + { + SwStartNode* pSttNd = new SwStartNode( aEndIdx, SwNodeType::Start, + SwTableBoxStartNode ); + pSttNd->m_pStartOfSection = pTableNd; + new SwEndNode( aEndIdx, *pSttNd ); + + pPrvBox = new SwTableBox( pBoxFormat, *pSttNd, pLine ); + + SwTableBoxes & rTabBoxes = pLine->GetTabBoxes(); + sal_uInt16 nRealInsPos = nInsPos + n; + if (nRealInsPos > rTabBoxes.size()) + nRealInsPos = rTabBoxes.size(); + + rTabBoxes.insert( rTabBoxes.begin() + nRealInsPos, pPrvBox ); + + if( ! pTextColl->IsAssignedToListLevelOfOutlineStyle() +//FEATURE::CONDCOLL + && RES_CONDTXTFMTCOLL != pTextColl->Which() +//FEATURE::CONDCOLL + ) + new SwTextNode( SwNodeIndex( *pSttNd->EndOfSectionNode() ), + pTextColl, pAutoAttr ); + else + { + // Handle Outline numbering correctly! + SwTextNode* pTNd = new SwTextNode( + SwNodeIndex( *pSttNd->EndOfSectionNode() ), + GetDoc().GetDfltTextFormatColl(), + pAutoAttr ); + pTNd->ChgFormatColl( pTextColl ); + } + } + return true; +} + +/** + * Insert a new Table + */ +const SwTable* SwDoc::InsertTable( const SwInsertTableOptions& rInsTableOpts, + const SwPosition& rPos, sal_uInt16 nRows, + sal_uInt16 nCols, sal_Int16 eAdjust, + const SwTableAutoFormat* pTAFormat, + const std::vector<sal_uInt16> *pColArr, + bool bCalledFromShell, + bool bNewModel ) +{ + assert(nRows && "Table without line?"); + assert(nCols && "Table without rows?"); + + { + // Do not copy into Footnotes! + if( rPos.nNode < GetNodes().GetEndOfInserts().GetIndex() && + rPos.nNode >= GetNodes().GetEndOfInserts().StartOfSectionIndex() ) + return nullptr; + + // If the ColumnArray has a wrong count, ignore it! + if( pColArr && + static_cast<size_t>(nCols + ( text::HoriOrientation::NONE == eAdjust ? 2 : 1 )) != pColArr->size() ) + pColArr = nullptr; + } + + OUString aTableName = GetUniqueTableName(); + + if( GetIDocumentUndoRedo().DoesUndo() ) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoInsTable>( rPos, nCols, nRows, o3tl::narrowing<sal_uInt16>(eAdjust), + rInsTableOpts, pTAFormat, pColArr, + aTableName)); + } + + // Start with inserting the Nodes and get the AutoFormat for the Table + SwTextFormatColl *pBodyColl = getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TABLE ), + *pHeadColl = pBodyColl; + + bool bDfltBorders( rInsTableOpts.mnInsMode & SwInsertTableFlags::DefaultBorder ); + + if( (rInsTableOpts.mnInsMode & SwInsertTableFlags::Headline) && (1 != nRows || !bDfltBorders) ) + pHeadColl = getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TABLE_HDLN ); + + const sal_uInt16 nRowsToRepeat = + SwInsertTableFlags::Headline == (rInsTableOpts.mnInsMode & SwInsertTableFlags::Headline) ? + rInsTableOpts.mnRowsToRepeat : + 0; + + /* Save content node to extract FRAMEDIR from. */ + const SwContentNode * pContentNd = rPos.nNode.GetNode().GetContentNode(); + + /* If we are called from a shell pass the attrset from + pContentNd (aka the node the table is inserted at) thus causing + SwNodes::InsertTable to propagate an adjust item if + necessary. */ + SwTableNode *pTableNd = SwNodes::InsertTable( + rPos.nNode, + nCols, + pBodyColl, + nRows, + nRowsToRepeat, + pHeadColl, + bCalledFromShell ? &pContentNd->GetSwAttrSet() : nullptr ); + + // Create the Box/Line/Table construct + SwTableLineFormat* pLineFormat = MakeTableLineFormat(); + SwTableFormat* pTableFormat = MakeTableFrameFormat( aTableName, GetDfltFrameFormat() ); + + /* If the node to insert the table at is a context node and has a + non-default FRAMEDIR propagate it to the table. */ + if (pContentNd) + { + const SwAttrSet & aNdSet = pContentNd->GetSwAttrSet(); + if (const SvxFrameDirectionItem* pItem = aNdSet.GetItemIfSet( RES_FRAMEDIR )) + { + pTableFormat->SetFormatAttr( *pItem ); + } + } + + // Set Orientation at the Table's Format + pTableFormat->SetFormatAttr( SwFormatHoriOrient( 0, eAdjust ) ); + // All lines use the left-to-right Fill-Order! + pLineFormat->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT )); + + // Set USHRT_MAX as the Table's default SSize + SwTwips nWidth = USHRT_MAX; + if( pColArr ) + { + sal_uInt16 nSttPos = pColArr->front(); + sal_uInt16 nLastPos = pColArr->back(); + if( text::HoriOrientation::NONE == eAdjust ) + { + sal_uInt16 nFrameWidth = nLastPos; + nLastPos = (*pColArr)[ pColArr->size()-2 ]; + pTableFormat->SetFormatAttr( SvxLRSpaceItem( nSttPos, nFrameWidth - nLastPos, 0, 0, RES_LR_SPACE ) ); + } + nWidth = nLastPos - nSttPos; + } + else + { + nWidth /= nCols; + nWidth *= nCols; // to avoid rounding problems + } + pTableFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nWidth )); + if( !(rInsTableOpts.mnInsMode & SwInsertTableFlags::SplitLayout) ) + pTableFormat->SetFormatAttr( SwFormatLayoutSplit( false )); + + // Move the hard PageDesc/PageBreak Attributes if needed + SwContentNode* pNextNd = GetNodes()[ pTableNd->EndOfSectionIndex()+1 ] + ->GetContentNode(); + if( pNextNd && pNextNd->HasSwAttrSet() ) + { + const SfxItemSet* pNdSet = pNextNd->GetpSwAttrSet(); + if( const SwFormatPageDesc* pItem = pNdSet->GetItemIfSet( RES_PAGEDESC, false ) ) + { + pTableFormat->SetFormatAttr( *pItem ); + pNextNd->ResetAttr( RES_PAGEDESC ); + pNdSet = pNextNd->GetpSwAttrSet(); + } + const SvxFormatBreakItem* pItem; + if( pNdSet && (pItem = pNdSet->GetItemIfSet( RES_BREAK, false )) ) + { + pTableFormat->SetFormatAttr( *pItem ); + pNextNd->ResetAttr( RES_BREAK ); + } + } + + SwTable& rNdTable = pTableNd->GetTable(); + rNdTable.RegisterToFormat( *pTableFormat ); + + rNdTable.SetRowsToRepeat( nRowsToRepeat ); + rNdTable.SetTableModel( bNewModel ); + + std::vector<SwTableBoxFormat*> aBoxFormatArr; + SwTableBoxFormat* pBoxFormat = nullptr; + if( !bDfltBorders && !pTAFormat ) + { + pBoxFormat = MakeTableBoxFormat(); + pBoxFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, USHRT_MAX / nCols, 0 )); + } + else + { + const sal_uInt16 nBoxArrLen = pTAFormat ? 16 : 4; + aBoxFormatArr.resize( nBoxArrLen, nullptr ); + } + SfxItemSetFixed<RES_CHRATR_BEGIN, RES_PARATR_LIST_END-1> aCharSet( GetAttrPool() ); + + SwNodeIndex aNdIdx( *pTableNd, 1 ); // Set to StartNode of first Box + SwTableLines& rLines = rNdTable.GetTabLines(); + for( sal_uInt16 n = 0; n < nRows; ++n ) + { + SwTableLine* pLine = new SwTableLine( pLineFormat, nCols, nullptr ); + rLines.insert( rLines.begin() + n, pLine ); + SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + for( sal_uInt16 i = 0; i < nCols; ++i ) + { + SwTableBoxFormat *pBoxF; + if( pTAFormat ) + { + sal_uInt8 nId = SwTableAutoFormat::CountPos(i, nCols, n, nRows); + pBoxF = ::lcl_CreateAFormatBoxFormat( *this, aBoxFormatArr, *pTAFormat, + nRows, nCols, nId ); + + // Set the Paragraph/Character Attributes if needed + if( pTAFormat->IsFont() || pTAFormat->IsJustify() ) + { + aCharSet.ClearItem(); + pTAFormat->UpdateToSet( nId, nRows==1, nCols==1, aCharSet, + SwTableAutoFormatUpdateFlags::Char, nullptr ); + if( aCharSet.Count() ) + GetNodes()[ aNdIdx.GetIndex()+1 ]->GetContentNode()-> + SetAttr( aCharSet ); + } + } + else if( bDfltBorders ) + { + sal_uInt8 nBoxId = (i < nCols - 1 ? 0 : 1) + (n ? 2 : 0 ); + pBoxF = ::lcl_CreateDfltBoxFormat( *this, aBoxFormatArr, nCols, nBoxId); + } + else + pBoxF = pBoxFormat; + + // For AutoFormat on input: the columns are set when inserting the Table + // The Array contains the columns positions and not their widths! + if( pColArr ) + { + nWidth = (*pColArr)[ i + 1 ] - (*pColArr)[ i ]; + if( pBoxF->GetFrameSize().GetWidth() != nWidth ) + { + if( pBoxF->HasWriterListeners() ) // Create new Format + { + SwTableBoxFormat *pNewFormat = MakeTableBoxFormat(); + *pNewFormat = *pBoxF; + pBoxF = pNewFormat; + } + pBoxF->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nWidth )); + } + } + + SwTableBox *pBox = new SwTableBox( pBoxF, aNdIdx, pLine); + rBoxes.insert( rBoxes.begin() + i, pBox ); + aNdIdx += SwNodeOffset(3); // StartNode, TextNode, EndNode == 3 Nodes + } + } + // Insert Frames + GetNodes().GoNext( &aNdIdx ); // Go to the next ContentNode + pTableNd->MakeOwnFrames( &aNdIdx ); + + // To-Do - add 'SwExtraRedlineTable' also ? + if( getIDocumentRedlineAccess().IsRedlineOn() || (!getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + SwPaM aPam( *pTableNd->EndOfSectionNode(), *pTableNd, SwNodeOffset(1) ); + if( getIDocumentRedlineAccess().IsRedlineOn() ) + getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true); + else + getIDocumentRedlineAccess().SplitRedline( aPam ); + } + + getIDocumentState().SetModified(); + CHECK_TABLE(rNdTable); + return &rNdTable; +} + +SwTableNode* SwNodes::InsertTable( const SwNodeIndex& rNdIdx, + sal_uInt16 nBoxes, + SwTextFormatColl* pContentTextColl, + sal_uInt16 nLines, + sal_uInt16 nRepeat, + SwTextFormatColl* pHeadlineTextColl, + const SwAttrSet * pAttrSet) +{ + if( !nBoxes ) + return nullptr; + + // If Lines is given, create the Matrix from Lines and Boxes + if( !pHeadlineTextColl || !nLines ) + pHeadlineTextColl = pContentTextColl; + + SwTableNode * pTableNd = new SwTableNode( rNdIdx ); + SwEndNode* pEndNd = new SwEndNode( rNdIdx, *pTableNd ); + + if( !nLines ) // For the for loop + ++nLines; + + SwNodeIndex aIdx( *pEndNd ); + SwTextFormatColl* pTextColl = pHeadlineTextColl; + for( sal_uInt16 nL = 0; nL < nLines; ++nL ) + { + for( sal_uInt16 nB = 0; nB < nBoxes; ++nB ) + { + SwStartNode* pSttNd = new SwStartNode( aIdx, SwNodeType::Start, + SwTableBoxStartNode ); + pSttNd->m_pStartOfSection = pTableNd; + + SwTextNode * pTmpNd = new SwTextNode( aIdx, pTextColl ); + + // #i60422# Propagate some more attributes. + const SfxPoolItem* pItem = nullptr; + if ( nullptr != pAttrSet ) + { + static const sal_uInt16 aPropagateItems[] = { + RES_PARATR_ADJUST, + RES_CHRATR_FONT, RES_CHRATR_FONTSIZE, + RES_CHRATR_CJK_FONT, RES_CHRATR_CJK_FONTSIZE, + RES_CHRATR_CTL_FONT, RES_CHRATR_CTL_FONTSIZE, 0 }; + + const sal_uInt16* pIdx = aPropagateItems; + while ( *pIdx != 0 ) + { + if ( SfxItemState::SET != pTmpNd->GetSwAttrSet().GetItemState( *pIdx ) && + SfxItemState::SET == pAttrSet->GetItemState( *pIdx, true, &pItem ) ) + static_cast<SwContentNode *>(pTmpNd)->SetAttr(*pItem); + ++pIdx; + } + } + + new SwEndNode( aIdx, *pSttNd ); + } + if ( nL + 1 >= nRepeat ) + pTextColl = pContentTextColl; + } + return pTableNd; +} + +/** + * Text to Table + */ +const SwTable* SwDoc::TextToTable( const SwInsertTableOptions& rInsTableOpts, + const SwPaM& rRange, sal_Unicode cCh, + sal_Int16 eAdjust, + const SwTableAutoFormat* pTAFormat ) +{ + // See if the selection contains a Table + const SwPosition *pStt = rRange.Start(), *pEnd = rRange.End(); + { + SwNodeOffset nCnt = pStt->nNode.GetIndex(); + for( ; nCnt <= pEnd->nNode.GetIndex(); ++nCnt ) + if( !GetNodes()[ nCnt ]->IsTextNode() ) + return nullptr; + } + + // Save first node in the selection if it is a context node + SwContentNode * pSttContentNd = pStt->nNode.GetNode().GetContentNode(); + + SwPaM aOriginal( *pStt, *pEnd ); + pStt = aOriginal.GetMark(); + pEnd = aOriginal.GetPoint(); + + SwUndoTextToTable* pUndo = nullptr; + if( GetIDocumentUndoRedo().DoesUndo() ) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::TEXTTOTABLE, nullptr ); + pUndo = new SwUndoTextToTable( aOriginal, rInsTableOpts, cCh, + o3tl::narrowing<sal_uInt16>(eAdjust), pTAFormat ); + GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + + // Do not add splitting the TextNode to the Undo history + GetIDocumentUndoRedo().DoUndo( false ); + } + + ::PaMCorrAbs( aOriginal, *pEnd ); + + // Make sure that the range is on Node Edges + SwNodeRange aRg( pStt->nNode, pEnd->nNode ); + if( pStt->nContent.GetIndex() ) + getIDocumentContentOperations().SplitNode( *pStt, false ); + + bool bEndContent = 0 != pEnd->nContent.GetIndex(); + + // Do not split at the End of a Line (except at the End of the Doc) + if( bEndContent ) + { + if( pEnd->nNode.GetNode().GetContentNode()->Len() != pEnd->nContent.GetIndex() + || pEnd->nNode.GetIndex() >= GetNodes().GetEndOfContent().GetIndex()-1 ) + { + getIDocumentContentOperations().SplitNode( *pEnd, false ); + --const_cast<SwNodeIndex&>(pEnd->nNode); + const_cast<SwIndex&>(pEnd->nContent).Assign( + pEnd->nNode.GetNode().GetContentNode(), 0 ); + // A Node and at the End? + if( pStt->nNode.GetIndex() >= pEnd->nNode.GetIndex() ) + --aRg.aStart; + } + else + ++aRg.aEnd; + } + + if( aRg.aEnd.GetIndex() == aRg.aStart.GetIndex() ) + { + OSL_FAIL( "empty range" ); + ++aRg.aEnd; + } + + // We always use Upper to insert the Table + SwNode2LayoutSaveUpperFrames aNode2Layout( aRg.aStart.GetNode() ); + + GetIDocumentUndoRedo().DoUndo( nullptr != pUndo ); + + // Create the Box/Line/Table construct + SwTableBoxFormat* pBoxFormat = MakeTableBoxFormat(); + SwTableLineFormat* pLineFormat = MakeTableLineFormat(); + SwTableFormat* pTableFormat = MakeTableFrameFormat( GetUniqueTableName(), GetDfltFrameFormat() ); + + // All Lines have a left-to-right Fill Order + pLineFormat->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT )); + // The Table's SSize is USHRT_MAX + pTableFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, USHRT_MAX )); + if( !(rInsTableOpts.mnInsMode & SwInsertTableFlags::SplitLayout) ) + pTableFormat->SetFormatAttr( SwFormatLayoutSplit( false )); + + /* If the first node in the selection is a context node and if it + has an item FRAMEDIR set (no default) propagate the item to the + replacing table. */ + if (pSttContentNd) + { + const SwAttrSet & aNdSet = pSttContentNd->GetSwAttrSet(); + if (const SvxFrameDirectionItem *pItem = aNdSet.GetItemIfSet( RES_FRAMEDIR ) ) + { + pTableFormat->SetFormatAttr( *pItem ); + } + } + + //Resolves: tdf#87977, tdf#78599, disable broadcasting modifications + //until after RegisterToFormat is completed + bool bEnableSetModified = getIDocumentState().IsEnableSetModified(); + getIDocumentState().SetEnableSetModified(false); + + SwTableNode* pTableNd = GetNodes().TextToTable( + aRg, cCh, pTableFormat, pLineFormat, pBoxFormat, + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ), pUndo ); + + SwTable& rNdTable = pTableNd->GetTable(); + + const sal_uInt16 nRowsToRepeat = + SwInsertTableFlags::Headline == (rInsTableOpts.mnInsMode & SwInsertTableFlags::Headline) ? + rInsTableOpts.mnRowsToRepeat : + 0; + rNdTable.SetRowsToRepeat(nRowsToRepeat); + + bool bUseBoxFormat = false; + if( !pBoxFormat->HasWriterListeners() ) + { + // The Box's Formats already have the right size, we must only set + // the right Border/AutoFormat. + bUseBoxFormat = true; + pTableFormat->SetFormatAttr( pBoxFormat->GetFrameSize() ); + delete pBoxFormat; + eAdjust = text::HoriOrientation::NONE; + } + + // Set Orientation in the Table's Format + pTableFormat->SetFormatAttr( SwFormatHoriOrient( 0, eAdjust ) ); + rNdTable.RegisterToFormat(*pTableFormat); + + if( pTAFormat || ( rInsTableOpts.mnInsMode & SwInsertTableFlags::DefaultBorder) ) + { + sal_uInt8 nBoxArrLen = pTAFormat ? 16 : 4; + std::unique_ptr< DfltBoxAttrList_t > aBoxFormatArr1; + std::optional< std::vector<SwTableBoxFormat*> > aBoxFormatArr2; + if( bUseBoxFormat ) + { + aBoxFormatArr1.reset(new DfltBoxAttrList_t( nBoxArrLen, nullptr )); + } + else + { + aBoxFormatArr2 = std::vector<SwTableBoxFormat*>( nBoxArrLen, nullptr ); + } + + SfxItemSetFixed<RES_CHRATR_BEGIN, RES_PARATR_LIST_END-1> aCharSet( GetAttrPool() ); + + SwHistory* pHistory = pUndo ? &pUndo->GetHistory() : nullptr; + + SwTableBoxFormat *pBoxF = nullptr; + SwTableLines& rLines = rNdTable.GetTabLines(); + const SwTableLines::size_type nRows = rLines.size(); + for( SwTableLines::size_type n = 0; n < nRows; ++n ) + { + SwTableBoxes& rBoxes = rLines[ n ]->GetTabBoxes(); + const SwTableBoxes::size_type nCols = rBoxes.size(); + for( SwTableBoxes::size_type i = 0; i < nCols; ++i ) + { + SwTableBox* pBox = rBoxes[ i ]; + bool bChgSz = false; + + if( pTAFormat ) + { + sal_uInt8 nId = static_cast<sal_uInt8>(!n ? 0 : (( n+1 == nRows ) + ? 12 : (4 * (1 + ((n-1) & 1 ))))); + nId = nId + static_cast<sal_uInt8>(!i ? 0 : + ( i+1 == nCols ? 3 : (1 + ((i-1) & 1)))); + if( bUseBoxFormat ) + ::lcl_SetDfltBoxAttr( *pBox, *aBoxFormatArr1, nId, pTAFormat ); + else + { + bChgSz = nullptr == (*aBoxFormatArr2)[ nId ]; + pBoxF = ::lcl_CreateAFormatBoxFormat( *this, *aBoxFormatArr2, + *pTAFormat, USHRT_MAX, USHRT_MAX, nId ); + } + + // Set Paragraph/Character Attributes if needed + if( pTAFormat->IsFont() || pTAFormat->IsJustify() ) + { + aCharSet.ClearItem(); + pTAFormat->UpdateToSet( nId, nRows==1, nCols==1, aCharSet, + SwTableAutoFormatUpdateFlags::Char, nullptr ); + if( aCharSet.Count() ) + { + SwNodeOffset nSttNd = pBox->GetSttIdx()+1; + SwNodeOffset nEndNd = pBox->GetSttNd()->EndOfSectionIndex(); + for( ; nSttNd < nEndNd; ++nSttNd ) + { + SwContentNode* pNd = GetNodes()[ nSttNd ]->GetContentNode(); + if( pNd ) + { + if( pHistory ) + { + SwRegHistory aReg( pNd, *pNd, pHistory ); + pNd->SetAttr( aCharSet ); + } + else + pNd->SetAttr( aCharSet ); + } + } + } + } + } + else + { + sal_uInt8 nId = (i < nCols - 1 ? 0 : 1) + (n ? 2 : 0 ); + if( bUseBoxFormat ) + ::lcl_SetDfltBoxAttr( *pBox, *aBoxFormatArr1, nId ); + else + { + bChgSz = nullptr == (*aBoxFormatArr2)[ nId ]; + pBoxF = ::lcl_CreateDfltBoxFormat( *this, *aBoxFormatArr2, + USHRT_MAX, nId ); + } + } + + if( !bUseBoxFormat ) + { + if( bChgSz ) + pBoxF->SetFormatAttr( pBox->GetFrameFormat()->GetFrameSize() ); + pBox->ChgFrameFormat( pBoxF ); + } + } + } + + if( bUseBoxFormat ) + { + for( sal_uInt8 i = 0; i < nBoxArrLen; ++i ) + { + delete (*aBoxFormatArr1)[ i ]; + } + } + } + + // Check the boxes for numbers + if( IsInsTableFormatNum() ) + { + for (size_t nBoxes = rNdTable.GetTabSortBoxes().size(); nBoxes; ) + { + ChkBoxNumFormat(*rNdTable.GetTabSortBoxes()[ --nBoxes ], false); + } + } + + SwNodeOffset nIdx = pTableNd->GetIndex(); + aNode2Layout.RestoreUpperFrames( GetNodes(), nIdx, nIdx + 1 ); + + { + SwPaM& rTmp = const_cast<SwPaM&>(rRange); // Point always at the Start + rTmp.DeleteMark(); + rTmp.GetPoint()->nNode = *pTableNd; + SwContentNode* pCNd = GetNodes().GoNext( &rTmp.GetPoint()->nNode ); + rTmp.GetPoint()->nContent.Assign( pCNd, 0 ); + } + + if( pUndo ) + { + GetIDocumentUndoRedo().EndUndo( SwUndoId::TEXTTOTABLE, nullptr ); + } + + getIDocumentState().SetEnableSetModified(bEnableSetModified); + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty(true, nullptr, SwNodeOffset(0)); + return &rNdTable; +} + +static void lcl_RemoveBreaks(SwContentNode & rNode, SwTableFormat *const pTableFormat) +{ + // delete old layout frames, new ones need to be created... + rNode.DelFrames(nullptr); + + if (!rNode.IsTextNode()) + { + return; + } + + SwTextNode & rTextNode = *rNode.GetTextNode(); + // remove PageBreaks/PageDesc/ColBreak + SfxItemSet const* pSet = rTextNode.GetpSwAttrSet(); + if (!pSet) + return; + + if (const SvxFormatBreakItem* pItem = pSet->GetItemIfSet(RES_BREAK, false)) + { + if (pTableFormat) + { + pTableFormat->SetFormatAttr(*pItem); + } + rTextNode.ResetAttr(RES_BREAK); + pSet = rTextNode.GetpSwAttrSet(); + } + + const SwFormatPageDesc* pPageDescItem; + if (pSet + && (pPageDescItem = pSet->GetItemIfSet(RES_PAGEDESC, false)) + && pPageDescItem->GetPageDesc()) + { + if (pTableFormat) + { + pTableFormat->SetFormatAttr(*pPageDescItem); + } + rTextNode.ResetAttr(RES_PAGEDESC); + } +} + +/** + * balance lines in table, insert empty boxes so all lines have the size + */ +static void +lcl_BalanceTable(SwTable & rTable, size_t const nMaxBoxes, + SwTableNode & rTableNd, SwTableBoxFormat & rBoxFormat, SwTextFormatColl & rTextColl, + SwUndoTextToTable *const pUndo, std::vector<sal_uInt16> *const pPositions) +{ + for (size_t n = 0; n < rTable.GetTabLines().size(); ++n) + { + SwTableLine *const pCurrLine = rTable.GetTabLines()[ n ]; + size_t const nBoxes = pCurrLine->GetTabBoxes().size(); + if (nMaxBoxes != nBoxes) + { + rTableNd.GetNodes().InsBoxen(&rTableNd, pCurrLine, &rBoxFormat, &rTextColl, + nullptr, nBoxes, nMaxBoxes - nBoxes); + + if (pUndo) + { + for (size_t i = nBoxes; i < nMaxBoxes; ++i) + { + pUndo->AddFillBox( *pCurrLine->GetTabBoxes()[i] ); + } + } + + // if the first line is missing boxes, the width array is useless! + if (!n && pPositions) + { + pPositions->clear(); + } + } + } +} + +static void +lcl_SetTableBoxWidths(SwTable & rTable, size_t const nMaxBoxes, + SwTableBoxFormat & rBoxFormat, SwDoc & rDoc, + std::vector<sal_uInt16> *const pPositions) +{ + if (pPositions && !pPositions->empty()) + { + SwTableLines& rLns = rTable.GetTabLines(); + sal_uInt16 nLastPos = 0; + for (size_t n = 0; n < pPositions->size(); ++n) + { + SwTableBoxFormat *pNewFormat = rDoc.MakeTableBoxFormat(); + pNewFormat->SetFormatAttr( + SwFormatFrameSize(SwFrameSize::Variable, (*pPositions)[n] - nLastPos)); + for (size_t nTmpLine = 0; nTmpLine < rLns.size(); ++nTmpLine) + { + // Have to do an Add here, because the BoxFormat + // is still needed by the caller + pNewFormat->Add( rLns[ nTmpLine ]->GetTabBoxes()[ n ] ); + } + + nLastPos = (*pPositions)[ n ]; + } + + // propagate size upwards from format, so the table gets the right size + SAL_WARN_IF(rBoxFormat.HasWriterListeners(), "sw.core", + "who is still registered in the format?"); + rBoxFormat.SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nLastPos )); + } + else + { + size_t nWidth = nMaxBoxes ? USHRT_MAX / nMaxBoxes : USHRT_MAX; + rBoxFormat.SetFormatAttr(SwFormatFrameSize(SwFrameSize::Variable, nWidth)); + } +} + +SwTableNode* SwNodes::TextToTable( const SwNodeRange& rRange, sal_Unicode cCh, + SwTableFormat* pTableFormat, + SwTableLineFormat* pLineFormat, + SwTableBoxFormat* pBoxFormat, + SwTextFormatColl* pTextColl, + SwUndoTextToTable* pUndo ) +{ + if( rRange.aStart >= rRange.aEnd ) + return nullptr; + + SwTableNode * pTableNd = new SwTableNode( rRange.aStart ); + new SwEndNode( rRange.aEnd, *pTableNd ); + + SwDoc& rDoc = GetDoc(); + std::vector<sal_uInt16> aPosArr; + SwTable& rTable = pTableNd->GetTable(); + SwTableBox* pBox; + sal_uInt16 nBoxes, nLines, nMaxBoxes = 0; + + SwNodeIndex aSttIdx( *pTableNd, 1 ); + SwNodeIndex aEndIdx( rRange.aEnd, -1 ); + for( nLines = 0, nBoxes = 0; + aSttIdx.GetIndex() < aEndIdx.GetIndex(); + aSttIdx += SwNodeOffset(2), nLines++, nBoxes = 0 ) + { + SwTextNode* pTextNd = aSttIdx.GetNode().GetTextNode(); + OSL_ENSURE( pTextNd, "Only add TextNodes to the Table" ); + + if( !nLines && 0x0b == cCh ) + { + cCh = 0x09; + + // Get the separator's position from the first Node, in order for the Boxes to be set accordingly + SwTextFrameInfo aFInfo( static_cast<SwTextFrame*>(pTextNd->getLayoutFrame( pTextNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() )) ); + if( aFInfo.IsOneLine() ) // only makes sense in this case + { + OUString const& rText(pTextNd->GetText()); + for (sal_Int32 nChPos = 0; nChPos < rText.getLength(); ++nChPos) + { + if (rText[nChPos] == cCh) + { + // sw_redlinehide: no idea if this makes any sense... + TextFrameIndex const nPos(aFInfo.GetFrame()->MapModelToView(pTextNd, nChPos)); + aPosArr.push_back( o3tl::narrowing<sal_uInt16>( + aFInfo.GetCharPos(nPos+TextFrameIndex(1), false)) ); + } + } + + aPosArr.push_back( + o3tl::narrowing<sal_uInt16>(aFInfo.GetFrame()->IsVertical() ? + aFInfo.GetFrame()->getFramePrintArea().Bottom() : + aFInfo.GetFrame()->getFramePrintArea().Right()) ); + + } + } + + lcl_RemoveBreaks(*pTextNd, (0 == nLines) ? pTableFormat : nullptr); + + // Set the TableNode as StartNode for all TextNodes in the Table + pTextNd->m_pStartOfSection = pTableNd; + + SwTableLine* pLine = new SwTableLine( pLineFormat, 1, nullptr ); + rTable.GetTabLines().insert(rTable.GetTabLines().begin() + nLines, pLine); + + SwStartNode* pSttNd; + SwPosition aCntPos( aSttIdx, SwIndex( pTextNd )); + + const std::shared_ptr< sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + pContentStore->Save(rDoc, aSttIdx.GetIndex(), SAL_MAX_INT32); + + if( T2T_PARA != cCh ) + { + for (sal_Int32 nChPos = 0; nChPos < pTextNd->GetText().getLength();) + { + if (pTextNd->GetText()[nChPos] == cCh) + { + aCntPos.nContent = nChPos; + std::function<void (SwTextNode *, sw::mark::RestoreMode, bool)> restoreFunc( + [&](SwTextNode *const pNewNode, sw::mark::RestoreMode const eMode, bool) + { + if (!pContentStore->Empty()) + { + pContentStore->Restore(*pNewNode, nChPos, nChPos + 1, eMode); + } + }); + SwContentNode *const pNewNd = + pTextNd->SplitContentNode(aCntPos, &restoreFunc); + + // Delete separator and correct search string + pTextNd->EraseText( aCntPos.nContent, 1 ); + nChPos = 0; + + // Set the TableNode as StartNode for all TextNodes in the Table + const SwNodeIndex aTmpIdx( aCntPos.nNode, -1 ); + pSttNd = new SwStartNode( aTmpIdx, SwNodeType::Start, + SwTableBoxStartNode ); + new SwEndNode( aCntPos.nNode, *pSttNd ); + pNewNd->m_pStartOfSection = pSttNd; + + // Assign Section to the Box + pBox = new SwTableBox( pBoxFormat, *pSttNd, pLine ); + pLine->GetTabBoxes().insert( pLine->GetTabBoxes().begin() + nBoxes++, pBox ); + } + else + { + ++nChPos; + } + } + } + + // Now for the last substring + if( !pContentStore->Empty()) + pContentStore->Restore( *pTextNd, pTextNd->GetText().getLength(), pTextNd->GetText().getLength()+1 ); + + pSttNd = new SwStartNode( aCntPos.nNode, SwNodeType::Start, SwTableBoxStartNode ); + const SwNodeIndex aTmpIdx( aCntPos.nNode, 1 ); + new SwEndNode( aTmpIdx, *pSttNd ); + pTextNd->m_pStartOfSection = pSttNd; + + pBox = new SwTableBox( pBoxFormat, *pSttNd, pLine ); + pLine->GetTabBoxes().insert( pLine->GetTabBoxes().begin() + nBoxes++, pBox ); + if( nMaxBoxes < nBoxes ) + nMaxBoxes = nBoxes; + } + + lcl_BalanceTable(rTable, nMaxBoxes, *pTableNd, *pBoxFormat, *pTextColl, + pUndo, &aPosArr); + lcl_SetTableBoxWidths(rTable, nMaxBoxes, *pBoxFormat, rDoc, &aPosArr); + + return pTableNd; +} + +const SwTable* SwDoc::TextToTable( const std::vector< std::vector<SwNodeRange> >& rTableNodes ) +{ + if (rTableNodes.empty()) + return nullptr; + + const std::vector<SwNodeRange>& rFirstRange = *rTableNodes.begin(); + + if (rFirstRange.empty()) + return nullptr; + + const std::vector<SwNodeRange>& rLastRange = *rTableNodes.rbegin(); + + if (rLastRange.empty()) + return nullptr; + + /* Save first node in the selection if it is a content node. */ + SwContentNode * pSttContentNd = rFirstRange.begin()->aStart.GetNode().GetContentNode(); + + const SwNodeRange& rStartRange = *rFirstRange.begin(); + const SwNodeRange& rEndRange = *rLastRange.rbegin(); + + //!!! not necessarily TextNodes !!! + SwPaM aOriginal( rStartRange.aStart, rEndRange.aEnd ); + const SwPosition *pStt = aOriginal.GetMark(); + const SwPosition *pEnd = aOriginal.GetPoint(); + + bool const bUndo(GetIDocumentUndoRedo().DoesUndo()); + if (bUndo) + { + // Do not add splitting the TextNode to the Undo history + GetIDocumentUndoRedo().DoUndo(false); + } + + ::PaMCorrAbs( aOriginal, *pEnd ); + + // make sure that the range is on Node Edges + SwNodeRange aRg( pStt->nNode, pEnd->nNode ); + if( pStt->nContent.GetIndex() ) + getIDocumentContentOperations().SplitNode( *pStt, false ); + + bool bEndContent = 0 != pEnd->nContent.GetIndex(); + + // Do not split at the End of a Line (except at the End of the Doc) + if( bEndContent ) + { + if( pEnd->nNode.GetNode().GetContentNode()->Len() != pEnd->nContent.GetIndex() + || pEnd->nNode.GetIndex() >= GetNodes().GetEndOfContent().GetIndex()-1 ) + { + getIDocumentContentOperations().SplitNode( *pEnd, false ); + --const_cast<SwNodeIndex&>(pEnd->nNode); + const_cast<SwIndex&>(pEnd->nContent).Assign( + pEnd->nNode.GetNode().GetContentNode(), 0 ); + // A Node and at the End? + if( pStt->nNode.GetIndex() >= pEnd->nNode.GetIndex() ) + --aRg.aStart; + } + else + ++aRg.aEnd; + } + + assert(aRg.aEnd == pEnd->nNode); + assert(aRg.aStart == pStt->nNode); + if( aRg.aEnd.GetIndex() == aRg.aStart.GetIndex() ) + { + OSL_FAIL( "empty range" ); + ++aRg.aEnd; + } + + + { + // TODO: this is not Undo-able - only good enough for file import + IDocumentRedlineAccess & rIDRA(getIDocumentRedlineAccess()); + SwNodeIndex const prev(rTableNodes.begin()->begin()->aStart, -1); + SwNodeIndex const* pPrev(&prev); + // pPrev could point to non-textnode now + for (const auto& rRow : rTableNodes) + { + for (const auto& rCell : rRow) + { + assert(SwNodeIndex(*pPrev, +1) == rCell.aStart); + SwPaM pam(rCell.aStart, 0, *pPrev, + (pPrev->GetNode().IsContentNode()) + ? pPrev->GetNode().GetContentNode()->Len() : 0); + rIDRA.SplitRedline(pam); + pPrev = &rCell.aEnd; + } + } + // another one to break between last cell and node after table + SwPaM pam(SwNodeIndex(*pPrev, +1), 0, *pPrev, + (pPrev->GetNode().IsContentNode()) + ? pPrev->GetNode().GetContentNode()->Len() : 0); + rIDRA.SplitRedline(pam); + } + + // We always use Upper to insert the Table + SwNode2LayoutSaveUpperFrames aNode2Layout( aRg.aStart.GetNode() ); + + GetIDocumentUndoRedo().DoUndo(bUndo); + + // Create the Box/Line/Table construct + SwTableBoxFormat* pBoxFormat = MakeTableBoxFormat(); + SwTableLineFormat* pLineFormat = MakeTableLineFormat(); + SwTableFormat* pTableFormat = MakeTableFrameFormat( GetUniqueTableName(), GetDfltFrameFormat() ); + + // All Lines have a left-to-right Fill Order + pLineFormat->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT )); + // The Table's SSize is USHRT_MAX + pTableFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, USHRT_MAX )); + + /* If the first node in the selection is a context node and if it + has an item FRAMEDIR set (no default) propagate the item to the + replacing table. */ + if (pSttContentNd) + { + const SwAttrSet & aNdSet = pSttContentNd->GetSwAttrSet(); + if (const SvxFrameDirectionItem* pItem = aNdSet.GetItemIfSet( RES_FRAMEDIR )) + { + pTableFormat->SetFormatAttr( *pItem ); + } + } + + //Resolves: tdf#87977, tdf#78599, disable broadcasting modifications + //until after RegisterToFormat is completed + bool bEnableSetModified = getIDocumentState().IsEnableSetModified(); + getIDocumentState().SetEnableSetModified(false); + + SwTableNode* pTableNd = GetNodes().TextToTable( + rTableNodes, pTableFormat, pLineFormat, pBoxFormat ); + + SwTable& rNdTable = pTableNd->GetTable(); + rNdTable.RegisterToFormat(*pTableFormat); + + if( !pBoxFormat->HasWriterListeners() ) + { + // The Box's Formats already have the right size, we must only set + // the right Border/AutoFormat. + pTableFormat->SetFormatAttr( pBoxFormat->GetFrameSize() ); + delete pBoxFormat; + } + + SwNodeOffset nIdx = pTableNd->GetIndex(); + aNode2Layout.RestoreUpperFrames( GetNodes(), nIdx, nIdx + 1 ); + + getIDocumentState().SetEnableSetModified(bEnableSetModified); + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + return &rNdTable; +} + +std::unique_ptr<SwNodeRange> SwNodes::ExpandRangeForTableBox(const SwNodeRange & rRange) +{ + bool bChanged = false; + + SwNodeIndex aNewStart = rRange.aStart; + SwNodeIndex aNewEnd = rRange.aEnd; + + SwNodeIndex aEndIndex = rRange.aEnd; + SwNodeIndex aIndex = rRange.aStart; + + while (aIndex < aEndIndex) + { + SwNode& rNode = aIndex.GetNode(); + + if (rNode.IsStartNode()) + { + // advance aIndex to the end node of this start node + SwNode * pEndNode = rNode.EndOfSectionNode(); + aIndex = *pEndNode; + + if (aIndex > aNewEnd) + { + aNewEnd = aIndex; + bChanged = true; + } + } + else if (rNode.IsEndNode()) + { + SwNode * pStartNode = rNode.StartOfSectionNode(); + SwNodeIndex aStartIndex = *pStartNode; + + if (aStartIndex < aNewStart) + { + aNewStart = aStartIndex; + bChanged = true; + } + } + + if (aIndex < aEndIndex) + ++aIndex; + } + + SwNode * pNode = &aIndex.GetNode(); + while (pNode->IsEndNode() && aIndex < Count() - 1) + { + SwNode * pStartNode = pNode->StartOfSectionNode(); + SwNodeIndex aStartIndex(*pStartNode); + aNewStart = aStartIndex; + aNewEnd = aIndex; + bChanged = true; + + ++aIndex; + pNode = &aIndex.GetNode(); + } + + std::unique_ptr<SwNodeRange> pResult; + if (bChanged) + pResult.reset(new SwNodeRange(aNewStart, aNewEnd)); + return pResult; +} + +static void +lcl_SetTableBoxWidths2(SwTable & rTable, size_t const nMaxBoxes, + SwTableBoxFormat & rBoxFormat, SwDoc & rDoc) +{ + // rhbz#820283, fdo#55462: set default box widths so table width is covered + SwTableLines & rLines = rTable.GetTabLines(); + for (size_t nTmpLine = 0; nTmpLine < rLines.size(); ++nTmpLine) + { + SwTableBoxes & rBoxes = rLines[nTmpLine]->GetTabBoxes(); + assert(!rBoxes.empty()); // ensured by convertToTable + size_t const nMissing = nMaxBoxes - rBoxes.size(); + if (nMissing) + { + // default width for box at the end of an incomplete line + SwTableBoxFormat *const pNewFormat = rDoc.MakeTableBoxFormat(); + size_t nWidth = nMaxBoxes ? USHRT_MAX / nMaxBoxes : USHRT_MAX; + pNewFormat->SetFormatAttr( SwFormatFrameSize(SwFrameSize::Variable, + nWidth * (nMissing + 1)) ); + pNewFormat->Add(rBoxes.back()); + } + } + size_t nWidth = nMaxBoxes ? USHRT_MAX / nMaxBoxes : USHRT_MAX; + // default width for all boxes not at the end of an incomplete line + rBoxFormat.SetFormatAttr(SwFormatFrameSize(SwFrameSize::Variable, nWidth)); +} + +SwTableNode* SwNodes::TextToTable( const SwNodes::TableRanges_t & rTableNodes, + SwTableFormat* pTableFormat, + SwTableLineFormat* pLineFormat, + SwTableBoxFormat* pBoxFormat ) +{ + if( rTableNodes.empty() ) + return nullptr; + + SwTableNode * pTableNd = new SwTableNode( rTableNodes.begin()->begin()->aStart ); + //insert the end node after the last text node + SwNodeIndex aInsertIndex( rTableNodes.rbegin()->rbegin()->aEnd ); + ++aInsertIndex; + + //!! ownership will be transferred in c-tor to SwNodes array. + //!! Thus no real problem here... + new SwEndNode( aInsertIndex, *pTableNd ); + + SwDoc& rDoc = GetDoc(); + SwTable& rTable = pTableNd->GetTable(); + SwTableBox* pBox; + sal_uInt16 nLines, nMaxBoxes = 0; + + SwNodeIndex aNodeIndex = rTableNodes.begin()->begin()->aStart; + // delete frames of all contained content nodes + for( nLines = 0; aNodeIndex <= rTableNodes.rbegin()->rbegin()->aEnd; ++aNodeIndex,++nLines ) + { + SwNode& rNode = aNodeIndex.GetNode(); + if( rNode.IsContentNode() ) + { + lcl_RemoveBreaks(static_cast<SwContentNode&>(rNode), + (0 == nLines) ? pTableFormat : nullptr); + } + } + + nLines = 0; + for( const auto& rRow : rTableNodes ) + { + sal_uInt16 nBoxes = 0; + SwTableLine* pLine = new SwTableLine( pLineFormat, 1, nullptr ); + rTable.GetTabLines().insert(rTable.GetTabLines().begin() + nLines, pLine); + + for( const auto& rCell : rRow ) + { + const SwNodeIndex aTmpIdx( rCell.aStart,0 ); + + SwNodeIndex aCellEndIdx(rCell.aEnd); + ++aCellEndIdx; + SwStartNode* pSttNd = new SwStartNode( aTmpIdx, SwNodeType::Start, + SwTableBoxStartNode ); + + // Quotation of http://nabble.documentfoundation.org/Some-strange-lines-by-taking-a-look-at-the-bt-of-fdo-51916-tp3994561p3994639.html + // SwNode's constructor adds itself to the same SwNodes array as the other node (pSttNd). + // So this statement is only executed for the side-effect. + new SwEndNode( aCellEndIdx, *pSttNd ); + + //set the start node on all node of the current cell + SwNodeIndex aCellNodeIdx = rCell.aStart; + for(;aCellNodeIdx <= rCell.aEnd; ++aCellNodeIdx ) + { + aCellNodeIdx.GetNode().m_pStartOfSection = pSttNd; + //skip start/end node pairs + if( aCellNodeIdx.GetNode().IsStartNode() ) + aCellNodeIdx.Assign(*aCellNodeIdx.GetNode().EndOfSectionNode()); + } + + // assign Section to the Box + pBox = new SwTableBox( pBoxFormat, *pSttNd, pLine ); + pLine->GetTabBoxes().insert( pLine->GetTabBoxes().begin() + nBoxes++, pBox ); + } + if( nMaxBoxes < nBoxes ) + nMaxBoxes = nBoxes; + + nLines++; + } + + lcl_SetTableBoxWidths2(rTable, nMaxBoxes, *pBoxFormat, rDoc); + + return pTableNd; +} + +/** + * Table to Text + */ +bool SwDoc::TableToText( const SwTableNode* pTableNd, sal_Unicode cCh ) +{ + if( !pTableNd ) + return false; + + // #i34471# + // If this is triggered by SwUndoTableToText::Repeat() nobody ever deleted + // the table cursor. + SwEditShell* pESh = GetEditShell(); + if (pESh && pESh->IsTableMode()) + pESh->ClearMark(); + + SwNodeRange aRg( *pTableNd, SwNodeOffset(0), *pTableNd->EndOfSectionNode() ); + std::unique_ptr<SwUndoTableToText> pUndo; + SwNodeRange* pUndoRg = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().ClearRedo(); + pUndoRg = new SwNodeRange( aRg.aStart, SwNodeOffset(-1), aRg.aEnd, SwNodeOffset(+1) ); + pUndo.reset(new SwUndoTableToText( pTableNd->GetTable(), cCh )); + } + + SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() ); + aMsgHint.m_eFlags = TBL_BOXNAME; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + bool bRet = GetNodes().TableToText( aRg, cCh, pUndo.get() ); + if( pUndoRg ) + { + ++pUndoRg->aStart; + --pUndoRg->aEnd; + pUndo->SetRange( *pUndoRg ); + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + delete pUndoRg; + } + + if( bRet ) + getIDocumentState().SetModified(); + + return bRet; +} + +namespace { + +/** + * Use the ForEach method from PtrArray to recreate Text from a Table. + * The Boxes can also contain Lines! + */ +struct DelTabPara +{ + SwTextNode* pLastNd; + SwNodes& rNds; + SwUndoTableToText* pUndo; + sal_Unicode cCh; + + DelTabPara( SwNodes& rNodes, sal_Unicode cChar, SwUndoTableToText* pU ) : + pLastNd(nullptr), rNds( rNodes ), pUndo( pU ), cCh( cChar ) {} +}; + +} + +// Forward declare so that the Lines and Boxes can use recursion +static void lcl_DelBox( SwTableBox* pBox, DelTabPara* pDelPara ); + +static void lcl_DelLine( SwTableLine* pLine, DelTabPara* pPara ) +{ + assert(pPara && "The parameters are missing!"); + DelTabPara aPara( *pPara ); + for( auto& rpBox : pLine->GetTabBoxes() ) + lcl_DelBox(rpBox, &aPara ); + if( pLine->GetUpper() ) // Is there a parent Box? + // Return the last TextNode + pPara->pLastNd = aPara.pLastNd; +} + +static void lcl_DelBox( SwTableBox* pBox, DelTabPara* pDelPara ) +{ + assert(pDelPara && "The parameters are missing"); + + // Delete the Box's Lines + if( !pBox->GetTabLines().empty() ) + { + for( SwTableLine* pLine : pBox->GetTabLines() ) + lcl_DelLine( pLine, pDelPara ); + } + else + { + SwDoc& rDoc = pDelPara->rNds.GetDoc(); + SwNodeRange aDelRg( *pBox->GetSttNd(), SwNodeOffset(0), + *pBox->GetSttNd()->EndOfSectionNode() ); + // Delete the Section + pDelPara->rNds.SectionUp( &aDelRg ); + const SwTextNode* pCurTextNd = nullptr; + if (T2T_PARA != pDelPara->cCh && pDelPara->pLastNd) + pCurTextNd = aDelRg.aStart.GetNode().GetTextNode(); + if (nullptr != pCurTextNd) + { + // Join the current text node with the last from the previous box if possible + SwNodeOffset nNdIdx = aDelRg.aStart.GetIndex(); + --aDelRg.aStart; + if( pDelPara->pLastNd == &aDelRg.aStart.GetNode() ) + { + // Inserting the separator + SwIndex aCntIdx( pDelPara->pLastNd, + pDelPara->pLastNd->GetText().getLength()); + pDelPara->pLastNd->InsertText( OUString(pDelPara->cCh), aCntIdx, + SwInsertFlags::EMPTYEXPAND ); + if( pDelPara->pUndo ) + pDelPara->pUndo->AddBoxPos( rDoc, nNdIdx, aDelRg.aEnd.GetIndex(), + aCntIdx.GetIndex() ); + + const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + const sal_Int32 nOldTextLen = aCntIdx.GetIndex(); + pContentStore->Save(rDoc, nNdIdx, SAL_MAX_INT32); + + pDelPara->pLastNd->JoinNext(); + + if( !pContentStore->Empty() ) + pContentStore->Restore( rDoc, pDelPara->pLastNd->GetIndex(), nOldTextLen ); + } + else if( pDelPara->pUndo ) + { + ++aDelRg.aStart; + pDelPara->pUndo->AddBoxPos( rDoc, nNdIdx, aDelRg.aEnd.GetIndex() ); + } + } + else if( pDelPara->pUndo ) + pDelPara->pUndo->AddBoxPos( rDoc, aDelRg.aStart.GetIndex(), aDelRg.aEnd.GetIndex() ); + --aDelRg.aEnd; + pDelPara->pLastNd = aDelRg.aEnd.GetNode().GetTextNode(); + + // Do not take over the NumberFormatting's adjustment + if( pDelPara->pLastNd && pDelPara->pLastNd->HasSwAttrSet() ) + pDelPara->pLastNd->ResetAttr( RES_PARATR_ADJUST ); + } +} + +bool SwNodes::TableToText( const SwNodeRange& rRange, sal_Unicode cCh, + SwUndoTableToText* pUndo ) +{ + // Is a Table selected? + if (rRange.aStart.GetIndex() >= rRange.aEnd.GetIndex()) + return false; + SwTableNode *const pTableNd(rRange.aStart.GetNode().GetTableNode()); + if (nullptr == pTableNd || + &rRange.aEnd.GetNode() != pTableNd->EndOfSectionNode() ) + return false; + + // If the Table was alone in a Section, create the Frames via the Table's Upper + SwNode2LayoutSaveUpperFrames * pNode2Layout = nullptr; + SwNodeIndex aFrameIdx( rRange.aStart ); + SwNode* pFrameNd = FindPrvNxtFrameNode( aFrameIdx, &rRange.aEnd.GetNode() ); + if( !pFrameNd ) + // Collect all Uppers + pNode2Layout = new SwNode2LayoutSaveUpperFrames(*pTableNd); + + // Delete the Frames + pTableNd->DelFrames(); + + // "Delete" the Table and merge all Lines/Boxes + DelTabPara aDelPara( *this, cCh, pUndo ); + for( SwTableLine *pLine : pTableNd->m_pTable->GetTabLines() ) + lcl_DelLine( pLine, &aDelPara ); + + // We just created a TextNode with fitting separator for every TableLine. + // Now we only need to delete the TableSection and create the Frames for the + // new TextNode. + SwNodeRange aDelRg( rRange.aStart, rRange.aEnd ); + + // If the Table has PageDesc/Break Attributes, carry them over to the + // first Text Node + { + // What about UNDO? + const SfxItemSet& rTableSet = pTableNd->m_pTable->GetFrameFormat()->GetAttrSet(); + const SvxFormatBreakItem* pBreak = rTableSet.GetItemIfSet( RES_BREAK, false ); + const SwFormatPageDesc* pDesc = rTableSet.GetItemIfSet( RES_PAGEDESC, false ); + + if( pBreak || pDesc ) + { + SwNodeIndex aIdx( *pTableNd ); + SwContentNode* pCNd = GoNext( &aIdx ); + if( pBreak ) + pCNd->SetAttr( *pBreak ); + if( pDesc ) + pCNd->SetAttr( *pDesc ); + } + } + + SectionUp( &aDelRg ); // Delete this Section and by that the Table + // #i28006# + SwNodeOffset nStt = aDelRg.aStart.GetIndex(), nEnd = aDelRg.aEnd.GetIndex(); + if( !pFrameNd ) + { + pNode2Layout->RestoreUpperFrames( *this, + aDelRg.aStart.GetIndex(), aDelRg.aEnd.GetIndex() ); + delete pNode2Layout; + } + else + { + SwContentNode *pCNd; + SwSectionNode *pSNd; + while( aDelRg.aStart.GetIndex() < nEnd ) + { + pCNd = aDelRg.aStart.GetNode().GetContentNode(); + if( nullptr != pCNd ) + { + if( pFrameNd->IsContentNode() ) + static_cast<SwContentNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(*pCNd); + else if( pFrameNd->IsTableNode() ) + static_cast<SwTableNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(aDelRg.aStart); + else if( pFrameNd->IsSectionNode() ) + static_cast<SwSectionNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(aDelRg.aStart); + pFrameNd = pCNd; + } + else + { + pSNd = aDelRg.aStart.GetNode().GetSectionNode(); + if( pSNd ) + { + if( !pSNd->GetSection().IsHidden() && !pSNd->IsContentHidden() ) + { + pSNd->MakeOwnFrames(&aFrameIdx, &aDelRg.aEnd); + break; + } + aDelRg.aStart = *pSNd->EndOfSectionNode(); + } + } + ++aDelRg.aStart; + } + } + + // #i28006# Fly frames have to be restored even if the table was + // #alone in the section + const SwFrameFormats& rFlyArr = *GetDoc().GetSpzFrameFormats(); + for( auto pFly : rFlyArr ) + { + SwFrameFormat *const pFormat = pFly; + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + SwPosition const*const pAPos = rAnchor.GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId())) && + nStt <= pAPos->nNode.GetIndex() && + pAPos->nNode.GetIndex() < nEnd ) + { + pFormat->MakeFrames(); + } + } + + return true; +} + +/** + * Inserting Columns/Rows + */ +void SwDoc::InsertCol( const SwCursor& rCursor, sal_uInt16 nCnt, bool bBehind ) +{ + if( !::CheckSplitCells( rCursor, nCnt + 1, SwTableSearchType::Col ) ) + return; + + // Find the Boxes via the Layout + SwSelBoxes aBoxes; + ::GetTableSel( rCursor, aBoxes, SwTableSearchType::Col ); + + if( !aBoxes.empty() ) + InsertCol( aBoxes, nCnt, bBehind ); +} + +bool SwDoc::InsertCol( const SwSelBoxes& rBoxes, sal_uInt16 nCnt, bool bBehind ) +{ + OSL_ENSURE( !rBoxes.empty(), "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + SwTable& rTable = pTableNd->GetTable(); + if( dynamic_cast<const SwDDETable*>( &rTable) != nullptr) + return false; + + SwTableSortBoxes aTmpLst; + std::unique_ptr<SwUndoTableNdsChg> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_INSCOL, rBoxes, *pTableNd, + 0, 0, nCnt, bBehind, false )); + aTmpLst.insert( rTable.GetTabSortBoxes() ); + } + + bool bRet(false); + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + SwTableFormulaUpdate aMsgHint( &rTable ); + aMsgHint.m_eFlags = TBL_BOXPTR; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + bRet = rTable.InsertCol(*this, rBoxes, nCnt, bBehind); + if (bRet) + { + getIDocumentState().SetModified(); + ::ClearFEShellTabCols(*this, nullptr); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + } + } + + if( pUndo && bRet ) + { + pUndo->SaveNewBoxes( *pTableNd, aTmpLst ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + return bRet; +} + +void SwDoc::InsertRow( const SwCursor& rCursor, sal_uInt16 nCnt, bool bBehind ) +{ + // Find the Boxes via the Layout + SwSelBoxes aBoxes; + GetTableSel( rCursor, aBoxes, SwTableSearchType::Row ); + + if( !aBoxes.empty() ) + InsertRow( aBoxes, nCnt, bBehind ); +} + +bool SwDoc::InsertRow( const SwSelBoxes& rBoxes, sal_uInt16 nCnt, bool bBehind ) +{ + OSL_ENSURE( !rBoxes.empty(), "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + SwTable& rTable = pTableNd->GetTable(); + if( dynamic_cast<const SwDDETable*>( &rTable) != nullptr) + return false; + + SwTableSortBoxes aTmpLst; + std::unique_ptr<SwUndoTableNdsChg> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_INSROW,rBoxes, *pTableNd, + 0, 0, nCnt, bBehind, false )); + aTmpLst.insert( rTable.GetTabSortBoxes() ); + } + + bool bRet(false); + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + SwTableFormulaUpdate aMsgHint( &rTable ); + aMsgHint.m_eFlags = TBL_BOXPTR; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + bRet = rTable.InsertRow( this, rBoxes, nCnt, bBehind ); + if (bRet) + { + getIDocumentState().SetModified(); + ::ClearFEShellTabCols(*this, nullptr); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + } + } + + if( pUndo && bRet ) + { + pUndo->SaveNewBoxes( *pTableNd, aTmpLst ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + return bRet; + +} + +/** + * Deleting Columns/Rows + */ +void SwDoc::DeleteRow( const SwCursor& rCursor ) +{ + // Find the Boxes via the Layout + SwSelBoxes aBoxes; + GetTableSel( rCursor, aBoxes, SwTableSearchType::Row ); + if( ::HasProtectedCells( aBoxes )) + return; + + // Remove the Cursor from the to-be-deleted Section. + // The Cursor is placed after the table, except for + // - when there's another Line, we place it in that one + // - when a Line precedes it, we place it in that one + { + SwTableNode* pTableNd = rCursor.GetNode().FindTableNode(); + + if(dynamic_cast<const SwDDETable*>( & pTableNd->GetTable()) != nullptr) + return; + + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( aBoxes, &aFndBox ); + ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara ); + } + + if( aFndBox.GetLines().empty() ) + return; + + if (SwEditShell* pESh = GetEditShell()) + { + pESh->KillPams(); + // FIXME: actually we should be iterating over all Shells! + } + + FndBox_* pFndBox = &aFndBox; + while( 1 == pFndBox->GetLines().size() && + 1 == pFndBox->GetLines().front()->GetBoxes().size() ) + { + FndBox_ *const pTmp = pFndBox->GetLines().front()->GetBoxes()[0].get(); + if( pTmp->GetBox()->GetSttNd() ) + break; // Else it gets too far + pFndBox = pTmp; + } + + SwTableLine* pDelLine = pFndBox->GetLines().back()->GetLine(); + SwTableBox* pDelBox = pDelLine->GetTabBoxes().back(); + while( !pDelBox->GetSttNd() ) + { + SwTableLine* pLn = pDelBox->GetTabLines()[ + pDelBox->GetTabLines().size()-1 ]; + pDelBox = pLn->GetTabBoxes().back(); + } + SwTableBox* pNextBox = pDelLine->FindNextBox( pTableNd->GetTable(), + pDelBox ); + while( pNextBox && + pNextBox->GetFrameFormat()->GetProtect().IsContentProtected() ) + pNextBox = pNextBox->FindNextBox( pTableNd->GetTable(), pNextBox ); + + if( !pNextBox ) // No succeeding Boxes? Then take the preceding one + { + pDelLine = pFndBox->GetLines().front()->GetLine(); + pDelBox = pDelLine->GetTabBoxes()[ 0 ]; + while( !pDelBox->GetSttNd() ) + pDelBox = pDelBox->GetTabLines()[0]->GetTabBoxes()[0]; + pNextBox = pDelLine->FindPreviousBox( pTableNd->GetTable(), + pDelBox ); + while( pNextBox && + pNextBox->GetFrameFormat()->GetProtect().IsContentProtected() ) + pNextBox = pNextBox->FindPreviousBox( pTableNd->GetTable(), pNextBox ); + } + + SwNodeOffset nIdx; + if( pNextBox ) // Place the Cursor here + nIdx = pNextBox->GetSttIdx() + 1; + else // Else after the Table + nIdx = pTableNd->EndOfSectionIndex() + 1; + + SwNodeIndex aIdx( GetNodes(), nIdx ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = GetNodes().GoNext( &aIdx ); + + if( pCNd ) + { + // Change the Shell's Cursor or the one passed? + SwPaM* pPam = const_cast<SwPaM*>(static_cast<SwPaM const *>(&rCursor)); + pPam->GetPoint()->nNode = aIdx; + pPam->GetPoint()->nContent.Assign( pCNd, 0 ); + pPam->SetMark(); // Both want a part of it + pPam->DeleteMark(); + } + } + + // Thus delete the Rows + GetIDocumentUndoRedo().StartUndo(SwUndoId::ROW_DELETE, nullptr); + DeleteRowCol( aBoxes ); + GetIDocumentUndoRedo().EndUndo(SwUndoId::ROW_DELETE, nullptr); +} + +void SwDoc::DeleteCol( const SwCursor& rCursor ) +{ + // Find the Boxes via the Layout + SwSelBoxes aBoxes; + GetTableSel( rCursor, aBoxes, SwTableSearchType::Col ); + if( ::HasProtectedCells( aBoxes )) + return; + + // The Cursors need to be removed from the to-be-deleted range. + // Always place them after/on top of the Table; they are always set + // to the old position via the document position. + if (SwEditShell* pESh = GetEditShell()) + { + const SwNode* pNd = rCursor.GetNode().FindTableBoxStartNode(); + pESh->ParkCursor( SwNodeIndex( *pNd ) ); + } + + // Thus delete the Columns + GetIDocumentUndoRedo().StartUndo(SwUndoId::COL_DELETE, nullptr); + DeleteRowCol(aBoxes, SwDoc::RowColMode::DeleteColumn); + GetIDocumentUndoRedo().EndUndo(SwUndoId::COL_DELETE, nullptr); +} + +bool SwDoc::DeleteRowCol(const SwSelBoxes& rBoxes, RowColMode const eMode) +{ + if (!(eMode & SwDoc::RowColMode::DeleteProtected) + && ::HasProtectedCells(rBoxes)) + { + return false; + } + + OSL_ENSURE( !rBoxes.empty(), "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + if (!(eMode & SwDoc::RowColMode::DeleteProtected) + && dynamic_cast<const SwDDETable*>(&pTableNd->GetTable()) != nullptr) + { + return false; + } + + ::ClearFEShellTabCols(*this, nullptr); + SwSelBoxes aSelBoxes( rBoxes ); + SwTable &rTable = pTableNd->GetTable(); + tools::Long nMin = 0; + tools::Long nMax = 0; + if( rTable.IsNewModel() ) + { + if (eMode & SwDoc::RowColMode::DeleteColumn) + rTable.ExpandColumnSelection( aSelBoxes, nMin, nMax ); + else + rTable.FindSuperfluousRows( aSelBoxes ); + } + + // Are we deleting the whole Table? + const SwNodeOffset nTmpIdx1 = pTableNd->GetIndex(); + const SwNodeOffset nTmpIdx2 = aSelBoxes.back()->GetSttNd()->EndOfSectionIndex() + 1; + if( pTableNd->GetTable().GetTabSortBoxes().size() == aSelBoxes.size() && + aSelBoxes[0]->GetSttIdx()-1 == nTmpIdx1 && + nTmpIdx2 == pTableNd->EndOfSectionIndex() ) + { + bool bNewTextNd = false; + // Is it alone in a FlyFrame? + SwNodeIndex aIdx( *pTableNd, -1 ); + const SwStartNode* pSttNd = aIdx.GetNode().GetStartNode(); + if( pSttNd ) + { + const SwNodeOffset nTableEnd = pTableNd->EndOfSectionIndex() + 1; + const SwNodeOffset nSectEnd = pSttNd->EndOfSectionIndex(); + if( nTableEnd == nSectEnd ) + { + if( SwFlyStartNode == pSttNd->GetStartNodeType() ) + { + SwFrameFormat* pFormat = pSttNd->GetFlyFormat(); + if( pFormat ) + { + // That's the FlyFormat we're looking for + getIDocumentLayoutAccess().DelLayoutFormat( pFormat ); + return true; + } + } + // No Fly? Thus Header or Footer: always leave a TextNode + // We can forget about Undo then! + bNewTextNd = true; + } + } + + // No Fly? Then it is a Header or Footer, so keep always a TextNode + ++aIdx; + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().ClearRedo(); + SwPaM aPaM( *pTableNd->EndOfSectionNode(), aIdx.GetNode() ); + + if( bNewTextNd ) + { + const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 ); + GetNodes().MakeTextNode( aTmpIdx, + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) ); + } + + // Save the cursors (UNO and otherwise) + SwPaM aSavePaM( SwNodeIndex( *pTableNd->EndOfSectionNode() ) ); + if( ! aSavePaM.Move( fnMoveForward, GoInNode ) ) + { + *aSavePaM.GetMark() = SwPosition( *pTableNd ); + aSavePaM.Move( fnMoveBackward, GoInNode ); + } + { + SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode()); + ::PaMCorrAbs(tmpPaM, *aSavePaM.GetMark()); + } + + // Move hard PageBreaks to the succeeding Node + bool bSavePageBreak = false, bSavePageDesc = false; + SwNodeOffset nNextNd = pTableNd->EndOfSectionIndex()+1; + SwContentNode* pNextNd = GetNodes()[ nNextNd ]->GetContentNode(); + if( pNextNd ) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + const SfxPoolItem *pItem; + if( SfxItemState::SET == pTableFormat->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + { + pNextNd->SetAttr( *pItem ); + bSavePageDesc = true; + } + + if( SfxItemState::SET == pTableFormat->GetItemState( RES_BREAK, + false, &pItem ) ) + { + pNextNd->SetAttr( *pItem ); + bSavePageBreak = true; + } + } + std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete(aPaM, SwDeleteFlags::Default)); + if( bNewTextNd ) + pUndo->SetTableDelLastNd(); + pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc ); + pUndo->SetTableName(pTableNd->GetTable().GetFrameFormat()->GetName()); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + else + { + if( bNewTextNd ) + { + const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 ); + GetNodes().MakeTextNode( aTmpIdx, + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) ); + } + + // Save the cursors (UNO and otherwise) + SwPaM aSavePaM( SwNodeIndex( *pTableNd->EndOfSectionNode() ) ); + if( ! aSavePaM.Move( fnMoveForward, GoInNode ) ) + { + *aSavePaM.GetMark() = SwPosition( *pTableNd ); + aSavePaM.Move( fnMoveBackward, GoInNode ); + } + { + SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode()); + ::PaMCorrAbs(tmpPaM, *aSavePaM.GetMark()); + } + + // Move hard PageBreaks to the succeeding Node + SwContentNode* pNextNd = GetNodes()[ pTableNd->EndOfSectionIndex()+1 ]->GetContentNode(); + if( pNextNd ) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + const SfxPoolItem *pItem; + if( SfxItemState::SET == pTableFormat->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + pNextNd->SetAttr( *pItem ); + + if( SfxItemState::SET == pTableFormat->GetItemState( RES_BREAK, + false, &pItem ) ) + pNextNd->SetAttr( *pItem ); + } + + pTableNd->DelFrames(); + getIDocumentContentOperations().DeleteSection( pTableNd ); + } + + if (SwFEShell* pFEShell = GetDocShell()->GetFEShell()) + pFEShell->UpdateTableStyleFormatting(); + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + + return true; + } + + std::unique_ptr<SwUndoTableNdsChg> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_DELBOX, aSelBoxes, *pTableNd, + nMin, nMax, 0, false, false )); + } + + bool bRet(false); + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() ); + aMsgHint.m_eFlags = TBL_BOXPTR; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + if (rTable.IsNewModel()) + { + if (eMode & SwDoc::RowColMode::DeleteColumn) + rTable.PrepareDeleteCol( nMin, nMax ); + rTable.FindSuperfluousRows( aSelBoxes ); + if (pUndo) + pUndo->ReNewBoxes( aSelBoxes ); + } + bRet = rTable.DeleteSel( this, aSelBoxes, nullptr, pUndo.get(), true, true ); + if (bRet) + { + if (SwFEShell* pFEShell = GetDocShell()->GetFEShell()) + pFEShell->UpdateTableStyleFormatting(); + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + } + } + + if( pUndo && bRet ) + { + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + + return bRet; +} + +/** + * Split up/merge Boxes in the Table + */ +bool SwDoc::SplitTable( const SwSelBoxes& rBoxes, bool bVert, sal_uInt16 nCnt, + bool bSameHeight ) +{ + OSL_ENSURE( !rBoxes.empty() && nCnt, "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + SwTable& rTable = pTableNd->GetTable(); + if( dynamic_cast<const SwDDETable*>( &rTable) != nullptr) + return false; + + std::vector<SwNodeOffset> aNdsCnts; + SwTableSortBoxes aTmpLst; + std::unique_ptr<SwUndoTableNdsChg> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_SPLIT, rBoxes, *pTableNd, 0, 0, + nCnt, bVert, bSameHeight )); + + aTmpLst.insert( rTable.GetTabSortBoxes() ); + if( !bVert ) + { + for (size_t n = 0; n < rBoxes.size(); ++n) + { + const SwStartNode* pSttNd = rBoxes[ n ]->GetSttNd(); + aNdsCnts.push_back( pSttNd->EndOfSectionIndex() - + pSttNd->GetIndex() ); + } + } + } + + bool bRet(false); + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + SwTableFormulaUpdate aMsgHint( &rTable ); + aMsgHint.m_eFlags = TBL_BOXPTR; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + if (bVert) + bRet = rTable.SplitCol(*this, rBoxes, nCnt); + else + bRet = rTable.SplitRow(*this, rBoxes, nCnt, bSameHeight); + + if (bRet) + { + if (SwFEShell* pFEShell = GetDocShell()->GetFEShell()) + pFEShell->UpdateTableStyleFormatting(); + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + } + } + + if( pUndo && bRet ) + { + if( bVert ) + pUndo->SaveNewBoxes( *pTableNd, aTmpLst ); + else + pUndo->SaveNewBoxes( *pTableNd, aTmpLst, rBoxes, aNdsCnts ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + + return bRet; +} + +TableMergeErr SwDoc::MergeTable( SwPaM& rPam ) +{ + // Check if the current cursor's Point/Mark are inside a Table + SwTableNode* pTableNd = rPam.GetNode().FindTableNode(); + if( !pTableNd ) + return TableMergeErr::NoSelection; + SwTable& rTable = pTableNd->GetTable(); + if( dynamic_cast<const SwDDETable*>( &rTable) != nullptr ) + return TableMergeErr::NoSelection; + TableMergeErr nRet = TableMergeErr::NoSelection; + if( !rTable.IsNewModel() ) + { + nRet =::CheckMergeSel( rPam ); + if( TableMergeErr::Ok != nRet ) + return nRet; + nRet = TableMergeErr::NoSelection; + } + + // #i33394# + GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_MERGE, nullptr ); + + RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags(); + getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + + std::unique_ptr<SwUndoTableMerge> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoTableMerge( rPam )); + + // Find the Boxes via the Layout + SwSelBoxes aBoxes; + SwSelBoxes aMerged; + SwTableBox* pMergeBox; + + if( !rTable.PrepareMerge( rPam, aBoxes, aMerged, &pMergeBox, pUndo.get() ) ) + { // No cells found to merge + getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + if( pUndo ) + { + pUndo.reset(); + SwUndoId nLastUndoId(SwUndoId::EMPTY); + if (GetIDocumentUndoRedo().GetLastUndoInfo(nullptr, & nLastUndoId) + && (SwUndoId::REDLINE == nLastUndoId)) + { + // FIXME: why is this horrible cleanup necessary? + SwUndoRedline *const pU = dynamic_cast<SwUndoRedline*>( + GetUndoManager().RemoveLastUndo()); + if (pU && pU->GetRedlSaveCount()) + { + SwEditShell *const pEditShell(GetEditShell()); + assert(pEditShell); + ::sw::UndoRedoContext context(*this, *pEditShell); + static_cast<SfxUndoAction *>(pU)->UndoWithContext(context); + } + delete pU; + } + } + } + else + { + // The PaMs need to be removed from the to-be-deleted range. Thus always place + // them at the end of/on top of the Table; it's always set to the old position via + // the Document Position. + // For a start remember an index for the temporary position, because we cannot + // access it after GetMergeSel + { + rPam.DeleteMark(); + rPam.GetPoint()->nNode = *pMergeBox->GetSttNd(); + rPam.GetPoint()->nContent.Assign( nullptr, 0 ); + rPam.SetMark(); + rPam.DeleteMark(); + + SwPaM* pTmp = &rPam; + while( &rPam != ( pTmp = pTmp->GetNext() )) + for( int i = 0; i < 2; ++i ) + pTmp->GetBound( static_cast<bool>(i) ) = *rPam.GetPoint(); + + if (SwTableCursor* pTableCursor = dynamic_cast<SwTableCursor*>(&rPam)) + { + // tdf#135098 update selection so rPam's m_SelectedBoxes is updated + // to not contain the soon to-be-deleted SwTableBox so if the rPam + // is queried via a11y it doesn't claim the deleted cell still + // exists + pTableCursor->NewTableSelection(); + } + } + + // Merge them + SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() ); + aMsgHint.m_eFlags = TBL_BOXPTR; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + if( pTableNd->GetTable().Merge( this, aBoxes, aMerged, pMergeBox, pUndo.get() )) + { + nRet = TableMergeErr::Ok; + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + if( pUndo ) + { + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + } + + rPam.GetPoint()->nNode = *pMergeBox->GetSttNd(); + rPam.Move(); + + ::ClearFEShellTabCols(*this, nullptr); + getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + GetIDocumentUndoRedo().EndUndo( SwUndoId::TABLE_MERGE, nullptr ); + return nRet; +} + +SwTableNode::SwTableNode( const SwNodeIndex& rIdx ) + : SwStartNode( rIdx, SwNodeType::Table ) +{ + m_pTable.reset(new SwTable); +} + +SwTableNode::~SwTableNode() +{ + // Notify UNO wrappers + GetTable().GetFrameFormat()->GetNotifier().Broadcast(SfxHint(SfxHintId::Dying)); + DelFrames(); + m_pTable->SetTableNode(this); // set this so that ~SwDDETable can read it! + m_pTable.reset(); +} + +SwTabFrame *SwTableNode::MakeFrame( SwFrame* pSib ) +{ + return new SwTabFrame( *m_pTable, pSib ); +} + +/** + * Creates all Views from the Document for the preceding Node. The resulting ContentFrames + * are added to the corresponding Layout. + */ +void SwTableNode::MakeFramesForAdjacentContentNode(const SwNodeIndex & rIdx) +{ + if( !GetTable().GetFrameFormat()->HasWriterListeners()) // Do we actually have Frame? + return; + + SwFrame *pFrame; + SwContentNode * pNode = rIdx.GetNode().GetContentNode(); + + OSL_ENSURE( pNode, "No ContentNode or CopyNode and new Node is identical"); + + bool bBefore = rIdx < GetIndex(); + + SwNode2Layout aNode2Layout( *this, rIdx.GetIndex() ); + + while( nullptr != (pFrame = aNode2Layout.NextFrame()) ) + { + if ( ( pFrame->getRootFrame()->HasMergedParas() && + !pNode->IsCreateFrameWhenHidingRedlines() ) || + // tdf#153819 table deletion with change tracking: + // table node without frames in Hide Changes mode + !pFrame->GetUpper() ) + { + continue; + } + SwFrame *pNew = pNode->MakeFrame( pFrame ); + // Will the Node receive Frames before or after? + if ( bBefore ) + // The new one precedes me + pNew->Paste( pFrame->GetUpper(), pFrame ); + else + // The new one succeeds me + pNew->Paste( pFrame->GetUpper(), pFrame->GetNext() ); + } +} + +/** + * Create a TableFrame for every Shell and insert before the corresponding ContentFrame. + */ +void SwTableNode::MakeOwnFrames(SwNodeIndex* pIdxBehind) +{ + OSL_ENSURE( pIdxBehind, "No Index" ); + *pIdxBehind = *this; + SwNode *pNd = GetNodes().FindPrvNxtFrameNode( *pIdxBehind, EndOfSectionNode() ); + if( !pNd ) + return ; + + SwFrame *pFrame( nullptr ); + SwLayoutFrame *pUpper( nullptr ); + SwNode2Layout aNode2Layout( *pNd, GetIndex() ); + while( nullptr != (pUpper = aNode2Layout.UpperFrame( pFrame, *this )) ) + { + if (pUpper->getRootFrame()->HasMergedParas() + && !IsCreateFrameWhenHidingRedlines()) + { + continue; + } + SwTabFrame* pNew = MakeFrame( pUpper ); + pNew->Paste( pUpper, pFrame ); + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for next paragraph will change + // and relation CONTENT_FLOWS_TO for previous paragraph will change. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + { + SwViewShell* pViewShell( pNew->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + auto pNext = pNew->FindNextCnt( true ); + auto pPrev = pNew->FindPrevCnt(); + pViewShell->InvalidateAccessibleParaFlowRelation( + pNext ? pNext->DynCastTextFrame() : nullptr, + pPrev ? pPrev->DynCastTextFrame() : nullptr ); + } + } +#endif + pNew->RegistFlys(); + } +} + +void SwTableNode::DelFrames(SwRootFrame const*const pLayout) +{ + /* For a start, cut out and delete the TabFrames (which will also delete the Columns and Rows) + The TabFrames are attached to the FrameFormat of the SwTable. + We need to delete them in a more cumbersome way, for the Master to also delete the Follows. */ + + SwIterator<SwTabFrame,SwFormat> aIter( *(m_pTable->GetFrameFormat()) ); + SwTabFrame *pFrame = aIter.First(); + while ( pFrame ) + { + bool bAgain = false; + { + if (!pFrame->IsFollow() && (!pLayout || pLayout == pFrame->getRootFrame())) + { + while ( pFrame->HasFollow() ) + pFrame->JoinAndDelFollows(); + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for current next paragraph will change + // and relation CONTENT_FLOWS_TO for current previous paragraph will change. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + { + SwViewShell* pViewShell( pFrame->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + auto pNext = pFrame->FindNextCnt( true ); + auto pPrev = pFrame->FindPrevCnt(); + pViewShell->InvalidateAccessibleParaFlowRelation( + pNext ? pNext->DynCastTextFrame() : nullptr, + pPrev ? pPrev->DynCastTextFrame() : nullptr ); + } + } +#endif + if (pFrame->GetUpper()) + pFrame->Cut(); + SwFrame::DestroyFrame(pFrame); + bAgain = true; + } + } + pFrame = bAgain ? aIter.First() : aIter.Next(); + } +} + +void SwTableNode::SetNewTable( std::unique_ptr<SwTable> pNewTable, bool bNewFrames ) +{ + DelFrames(); + m_pTable->SetTableNode(this); + m_pTable = std::move(pNewTable); + if( bNewFrames ) + { + SwNodeIndex aIdx( *EndOfSectionNode()); + GetNodes().GoNext( &aIdx ); + MakeOwnFrames(&aIdx); + } +} + +void SwTableNode::RemoveRedlines() +{ + SwDoc& rDoc = GetDoc(); + SwTable& rTable = GetTable(); + rDoc.getIDocumentRedlineAccess().GetExtraRedlineTable().DeleteAllTableRedlines(rDoc, rTable, true, RedlineType::Any); +} + +void SwTableNode::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTableNode")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), BAD_CAST(OString::number(sal_Int32(GetIndex())).getStr())); + + if (m_pTable) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTable")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", m_pTable.get()); + m_pTable->GetFrameFormat()->dumpAsXml(pWriter); + for (const auto& pLine : m_pTable->GetTabLines()) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTableLine")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", pLine); + pLine->GetFrameFormat()->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); + } + + // (void)xmlTextWriterEndElement(pWriter); - it is a start node, so don't end, will make xml better nested +} + +void SwDoc::GetTabCols( SwTabCols &rFill, const SwCellFrame* pBoxFrame ) +{ + OSL_ENSURE( pBoxFrame, "pBoxFrame needs to be specified!" ); + if( !pBoxFrame ) + return; + + SwTabFrame *pTab = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoxFrame))->ImplFindTabFrame(); + const SwTableBox* pBox = pBoxFrame->GetTabBox(); + + // Set fixed points, LeftMin in Document coordinates, all others relative + SwRectFnSet aRectFnSet(pTab); + const SwPageFrame* pPage = pTab->FindPageFrame(); + const sal_uLong nLeftMin = aRectFnSet.GetLeft(pTab->getFrameArea()) - + aRectFnSet.GetLeft(pPage->getFrameArea()); + const sal_uLong nRightMax = aRectFnSet.GetRight(pTab->getFrameArea()) - + aRectFnSet.GetLeft(pPage->getFrameArea()); + + rFill.SetLeftMin ( nLeftMin ); + rFill.SetLeft ( aRectFnSet.GetLeft(pTab->getFramePrintArea()) ); + rFill.SetRight ( aRectFnSet.GetRight(pTab->getFramePrintArea())); + rFill.SetRightMax( nRightMax - nLeftMin ); + + pTab->GetTable()->GetTabCols( rFill, pBox ); +} + +// Here are some little helpers used in SwDoc::GetTabRows + +#define ROWFUZZY 25 + +namespace { + +struct FuzzyCompare +{ + bool operator() ( tools::Long s1, tools::Long s2 ) const; +}; + +} + +bool FuzzyCompare::operator() ( tools::Long s1, tools::Long s2 ) const +{ + return ( s1 < s2 && std::abs( s1 - s2 ) > ROWFUZZY ); +} + +static bool lcl_IsFrameInColumn( const SwCellFrame& rFrame, SwSelBoxes const & rBoxes ) +{ + for (size_t i = 0; i < rBoxes.size(); ++i) + { + if ( rFrame.GetTabBox() == rBoxes[ i ] ) + return true; + } + + return false; +} + +void SwDoc::GetTabRows( SwTabCols &rFill, const SwCellFrame* pBoxFrame ) +{ + OSL_ENSURE( pBoxFrame, "GetTabRows called without pBoxFrame" ); + + // Make code robust: + if ( !pBoxFrame ) + return; + + // #i39552# Collection of the boxes of the current + // column has to be done at the beginning of this function, because + // the table may be formatted in ::GetTableSel. + SwDeletionChecker aDelCheck( pBoxFrame ); + + SwSelBoxes aBoxes; + const SwContentFrame* pContent = ::GetCellContent( *pBoxFrame ); + if ( pContent && pContent->IsTextFrame() ) + { + const SwPosition aPos(*static_cast<const SwTextFrame*>(pContent)->GetTextNodeFirst()); + const SwCursor aTmpCursor( aPos, nullptr ); + ::GetTableSel( aTmpCursor, aBoxes, SwTableSearchType::Col ); + } + + // Make code robust: + if ( aDelCheck.HasBeenDeleted() ) + { + OSL_FAIL( "Current box has been deleted during GetTabRows()" ); + return; + } + + // Make code robust: + const SwTabFrame* pTab = pBoxFrame->FindTabFrame(); + OSL_ENSURE( pTab, "GetTabRows called without a table" ); + if ( !pTab ) + return; + + const SwFrame* pFrame = pTab->GetNextLayoutLeaf(); + + // Set fixed points, LeftMin in Document coordinates, all others relative + SwRectFnSet aRectFnSet(pTab); + const SwPageFrame* pPage = pTab->FindPageFrame(); + const tools::Long nLeftMin = ( aRectFnSet.IsVert() ? + pTab->GetPrtLeft() - pPage->getFrameArea().Left() : + pTab->GetPrtTop() - pPage->getFrameArea().Top() ); + const tools::Long nLeft = aRectFnSet.IsVert() ? LONG_MAX : 0; + const tools::Long nRight = aRectFnSet.GetHeight(pTab->getFramePrintArea()); + const tools::Long nRightMax = aRectFnSet.IsVert() ? nRight : LONG_MAX; + + rFill.SetLeftMin( nLeftMin ); + rFill.SetLeft( nLeft ); + rFill.SetRight( nRight ); + rFill.SetRightMax( nRightMax ); + + typedef std::map< tools::Long, std::pair< tools::Long, long >, FuzzyCompare > BoundaryMap; + BoundaryMap aBoundaries; + BoundaryMap::iterator aIter; + std::pair< tools::Long, long > aPair; + + typedef std::map< tools::Long, bool > HiddenMap; + HiddenMap aHidden; + HiddenMap::iterator aHiddenIter; + + while ( pFrame && pTab->IsAnLower( pFrame ) ) + { + if ( pFrame->IsCellFrame() && pFrame->FindTabFrame() == pTab ) + { + // upper and lower borders of current cell frame: + tools::Long nUpperBorder = aRectFnSet.GetTop(pFrame->getFrameArea()); + tools::Long nLowerBorder = aRectFnSet.GetBottom(pFrame->getFrameArea()); + + // get boundaries for nUpperBorder: + aIter = aBoundaries.find( nUpperBorder ); + if ( aIter == aBoundaries.end() ) + { + aPair.first = nUpperBorder; aPair.second = LONG_MAX; + aBoundaries[ nUpperBorder ] = aPair; + } + + // get boundaries for nLowerBorder: + aIter = aBoundaries.find( nLowerBorder ); + if ( aIter == aBoundaries.end() ) + { + aPair.first = nUpperBorder; aPair.second = LONG_MAX; + } + else + { + nLowerBorder = (*aIter).first; + tools::Long nNewLowerBorderUpperBoundary = std::max( (*aIter).second.first, nUpperBorder ); + aPair.first = nNewLowerBorderUpperBoundary; aPair.second = LONG_MAX; + } + aBoundaries[ nLowerBorder ] = aPair; + + // calculate hidden flags for entry nUpperBorder/nLowerBorder: + tools::Long nTmpVal = nUpperBorder; + for ( sal_uInt8 i = 0; i < 2; ++i ) + { + aHiddenIter = aHidden.find( nTmpVal ); + if ( aHiddenIter == aHidden.end() ) + aHidden[ nTmpVal ] = !lcl_IsFrameInColumn( *static_cast<const SwCellFrame*>(pFrame), aBoxes ); + else + { + if ( aHidden[ nTmpVal ] && + lcl_IsFrameInColumn( *static_cast<const SwCellFrame*>(pFrame), aBoxes ) ) + aHidden[ nTmpVal ] = false; + } + nTmpVal = nLowerBorder; + } + } + + pFrame = pFrame->GetNextLayoutLeaf(); + } + + // transfer calculated values from BoundaryMap and HiddenMap into rFill: + size_t nIdx = 0; + for ( const auto& rEntry : aBoundaries ) + { + const tools::Long nTabTop = aRectFnSet.GetPrtTop(*pTab); + const tools::Long nKey = aRectFnSet.YDiff( rEntry.first, nTabTop ); + const std::pair< tools::Long, long > aTmpPair = rEntry.second; + const tools::Long nFirst = aRectFnSet.YDiff( aTmpPair.first, nTabTop ); + const tools::Long nSecond = aTmpPair.second; + + aHiddenIter = aHidden.find( rEntry.first ); + const bool bHidden = aHiddenIter != aHidden.end() && (*aHiddenIter).second; + rFill.Insert( nKey, nFirst, nSecond, bHidden, nIdx++ ); + } + + // delete first and last entry + OSL_ENSURE( rFill.Count(), "Deleting from empty vector. Fasten your seatbelts!" ); + // #i60818# There may be only one entry in rFill. Make + // code robust by checking count of rFill. + if ( rFill.Count() ) rFill.Remove( 0 ); + if ( rFill.Count() ) rFill.Remove( rFill.Count() - 1 ); + rFill.SetLastRowAllowedToChange( !pTab->HasFollowFlowLine() ); +} + +void SwDoc::SetTabCols( const SwTabCols &rNew, bool bCurRowOnly, + const SwCellFrame* pBoxFrame ) +{ + const SwTableBox* pBox = nullptr; + SwTabFrame *pTab = nullptr; + + if( pBoxFrame ) + { + pTab = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoxFrame))->ImplFindTabFrame(); + pBox = pBoxFrame->GetTabBox(); + } + else + { + OSL_ENSURE( false, "must specify pBoxFrame" ); + return ; + } + + // If the Table is still using relative values (USHRT_MAX) + // we need to switch to absolute ones. + SwTable& rTab = *pTab->GetTable(); + const SwFormatFrameSize& rTableFrameSz = rTab.GetFrameFormat()->GetFrameSize(); + SwRectFnSet aRectFnSet(pTab); + // #i17174# - With fix for #i9040# the shadow size is taken + // from the table width. Thus, add its left and right size to current table + // printing area width in order to get the correct table size attribute. + SwTwips nPrtWidth = aRectFnSet.GetWidth(pTab->getFramePrintArea()); + { + SvxShadowItem aShadow( rTab.GetFrameFormat()->GetShadow() ); + nPrtWidth += aShadow.CalcShadowSpace( SvxShadowItemSide::LEFT ) + + aShadow.CalcShadowSpace( SvxShadowItemSide::RIGHT ); + } + if( nPrtWidth != rTableFrameSz.GetWidth() ) + { + SwFormatFrameSize aSz( rTableFrameSz ); + aSz.SetWidth( nPrtWidth ); + rTab.GetFrameFormat()->SetFormatAttr( aSz ); + } + + SwTabCols aOld( rNew.Count() ); + + const SwPageFrame* pPage = pTab->FindPageFrame(); + const sal_uLong nLeftMin = aRectFnSet.GetLeft(pTab->getFrameArea()) - + aRectFnSet.GetLeft(pPage->getFrameArea()); + const sal_uLong nRightMax = aRectFnSet.GetRight(pTab->getFrameArea()) - + aRectFnSet.GetLeft(pPage->getFrameArea()); + + // Set fixed points, LeftMin in Document coordinates, all others relative + aOld.SetLeftMin ( nLeftMin ); + aOld.SetLeft ( aRectFnSet.GetLeft(pTab->getFramePrintArea()) ); + aOld.SetRight ( aRectFnSet.GetRight(pTab->getFramePrintArea())); + aOld.SetRightMax( nRightMax - nLeftMin ); + + rTab.GetTabCols( aOld, pBox ); + SetTabCols(rTab, rNew, aOld, pBox, bCurRowOnly ); +} + +void SwDoc::SetTabRows( const SwTabCols &rNew, bool bCurColOnly, + const SwCellFrame* pBoxFrame ) +{ + SwTabFrame *pTab = nullptr; + + if( pBoxFrame ) + { + pTab = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoxFrame))->ImplFindTabFrame(); + } + else + { + OSL_ENSURE( false, "must specify pBoxFrame" ); + return ; + } + + // If the Table is still using relative values (USHRT_MAX) + // we need to switch to absolute ones. + SwRectFnSet aRectFnSet(pTab); + SwTabCols aOld( rNew.Count() ); + + // Set fixed points, LeftMin in Document coordinates, all others relative + const SwPageFrame* pPage = pTab->FindPageFrame(); + + aOld.SetRight( aRectFnSet.GetHeight(pTab->getFramePrintArea()) ); + tools::Long nLeftMin; + if ( aRectFnSet.IsVert() ) + { + nLeftMin = pTab->GetPrtLeft() - pPage->getFrameArea().Left(); + aOld.SetLeft ( LONG_MAX ); + aOld.SetRightMax( aOld.GetRight() ); + + } + else + { + nLeftMin = pTab->GetPrtTop() - pPage->getFrameArea().Top(); + aOld.SetLeft ( 0 ); + aOld.SetRightMax( LONG_MAX ); + } + aOld.SetLeftMin ( nLeftMin ); + + GetTabRows( aOld, pBoxFrame ); + + GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_ATTR, nullptr ); + + // check for differences between aOld and rNew: + const size_t nCount = rNew.Count(); + const SwTable* pTable = pTab->GetTable(); + OSL_ENSURE( pTable, "My colleague told me, this couldn't happen" ); + + for ( size_t i = 0; i <= nCount; ++i ) + { + const size_t nIdxStt = aRectFnSet.IsVert() ? nCount - i : i - 1; + const size_t nIdxEnd = aRectFnSet.IsVert() ? nCount - i - 1 : i; + + const tools::Long nOldRowStart = i == 0 ? 0 : aOld[ nIdxStt ]; + const tools::Long nOldRowEnd = i == nCount ? aOld.GetRight() : aOld[ nIdxEnd ]; + const tools::Long nOldRowHeight = nOldRowEnd - nOldRowStart; + + const tools::Long nNewRowStart = i == 0 ? 0 : rNew[ nIdxStt ]; + const tools::Long nNewRowEnd = i == nCount ? rNew.GetRight() : rNew[ nIdxEnd ]; + const tools::Long nNewRowHeight = nNewRowEnd - nNewRowStart; + + const tools::Long nDiff = nNewRowHeight - nOldRowHeight; + if ( std::abs( nDiff ) >= ROWFUZZY ) + { + // For the old table model pTextFrame and pLine will be set for every box. + // For the new table model pTextFrame will be set if the box is not covered, + // but the pLine will be set if the box is not an overlapping box + // In the new table model the row height can be adjusted, + // when both variables are set. + const SwTextFrame* pTextFrame = nullptr; + const SwTableLine* pLine = nullptr; + + // Iterate over all SwCellFrames with Bottom = nOldPos + const SwFrame* pFrame = pTab->GetNextLayoutLeaf(); + while ( pFrame && pTab->IsAnLower( pFrame ) ) + { + if ( pFrame->IsCellFrame() && pFrame->FindTabFrame() == pTab ) + { + const tools::Long nLowerBorder = aRectFnSet.GetBottom(pFrame->getFrameArea()); + const sal_uLong nTabTop = aRectFnSet.GetPrtTop(*pTab); + if ( std::abs( aRectFnSet.YInc( nTabTop, nOldRowEnd ) - nLowerBorder ) <= ROWFUZZY ) + { + if ( !bCurColOnly || pFrame == pBoxFrame ) + { + const SwFrame* pContent = ::GetCellContent( static_cast<const SwCellFrame&>(*pFrame) ); + + if ( pContent && pContent->IsTextFrame() ) + { + const SwTableBox* pBox = static_cast<const SwCellFrame*>(pFrame)->GetTabBox(); + const sal_Int32 nRowSpan = pBox->getRowSpan(); + if( nRowSpan > 0 ) // Not overlapped + pTextFrame = static_cast<const SwTextFrame*>(pContent); + if( nRowSpan < 2 ) // Not overlapping for row height + pLine = pBox->GetUpper(); + if( pLine && pTextFrame ) // always for old table model + { + // The new row height must not to be calculated from an overlapping box + SwFormatFrameSize aNew( pLine->GetFrameFormat()->GetFrameSize() ); + const tools::Long nNewSize = aRectFnSet.GetHeight(pFrame->getFrameArea()) + nDiff; + if( nNewSize != aNew.GetHeight() ) + { + aNew.SetHeight( nNewSize ); + if ( SwFrameSize::Variable == aNew.GetHeightSizeType() ) + aNew.SetHeightSizeType( SwFrameSize::Minimum ); + // This position must not be in an overlapped box + const SwPosition aPos(*static_cast<const SwTextFrame*>(pContent)->GetTextNodeFirst()); + const SwCursor aTmpCursor( aPos, nullptr ); + SetRowHeight( aTmpCursor, aNew ); + // For the new table model we're done, for the old one + // there might be another (sub)row to adjust... + if( pTable->IsNewModel() ) + break; + } + pLine = nullptr; + } + } + } + } + } + pFrame = pFrame->GetNextLayoutLeaf(); + } + } + } + + GetIDocumentUndoRedo().EndUndo( SwUndoId::TABLE_ATTR, nullptr ); + + ::ClearFEShellTabCols(*this, nullptr); +} + +/** + * Direct access for UNO + */ +void SwDoc::SetTabCols(SwTable& rTab, const SwTabCols &rNew, const SwTabCols &rOld, + const SwTableBox *pStart, bool bCurRowOnly ) +{ + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoAttrTable>( *rTab.GetTableNode(), true )); + } + rTab.SetTabCols( rNew, rOld, pStart, bCurRowOnly ); + ::ClearFEShellTabCols(*this, nullptr); + getIDocumentState().SetModified(); +} + +void SwDoc::SetRowsToRepeat( SwTable &rTable, sal_uInt16 nSet ) +{ + if( nSet == rTable.GetRowsToRepeat() ) + return; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoTableHeadline>(rTable, rTable.GetRowsToRepeat(), nSet) ); + } + + rTable.SetRowsToRepeat(nSet); + const SwMsgPoolItem aChg(RES_TBLHEADLINECHG); + rTable.GetFrameFormat()->CallSwClientNotify(sw::LegacyModifyHint(&aChg, &aChg)); + getIDocumentState().SetModified(); +} + +void SwCollectTableLineBoxes::AddToUndoHistory( const SwContentNode& rNd ) +{ + if( m_pHistory ) + m_pHistory->Add( rNd.GetFormatColl(), rNd.GetIndex(), SwNodeType::Text ); +} + +void SwCollectTableLineBoxes::AddBox( const SwTableBox& rBox ) +{ + m_aPositionArr.push_back(m_nWidth); + SwTableBox* p = const_cast<SwTableBox*>(&rBox); + m_Boxes.push_back(p); + m_nWidth = m_nWidth + o3tl::narrowing<sal_uInt16>(rBox.GetFrameFormat()->GetFrameSize().GetWidth()); +} + +const SwTableBox* SwCollectTableLineBoxes::GetBoxOfPos( const SwTableBox& rBox ) +{ + const SwTableBox* pRet = nullptr; + + if( !m_aPositionArr.empty() ) + { + std::vector<sal_uInt16>::size_type n; + for( n = 0; n < m_aPositionArr.size(); ++n ) + if( m_aPositionArr[ n ] == m_nWidth ) + break; + else if( m_aPositionArr[ n ] > m_nWidth ) + { + if( n ) + --n; + break; + } + + if( n >= m_aPositionArr.size() ) + --n; + + m_nWidth = m_nWidth + o3tl::narrowing<sal_uInt16>(rBox.GetFrameFormat()->GetFrameSize().GetWidth()); + pRet = m_Boxes[ n ]; + } + return pRet; +} + +bool SwCollectTableLineBoxes::Resize( sal_uInt16 nOffset, sal_uInt16 nOldWidth ) +{ + if( !m_aPositionArr.empty() ) + { + std::vector<sal_uInt16>::size_type n; + for( n = 0; n < m_aPositionArr.size(); ++n ) + { + if( m_aPositionArr[ n ] == nOffset ) + break; + else if( m_aPositionArr[ n ] > nOffset ) + { + if( n ) + --n; + break; + } + } + + m_aPositionArr.erase( m_aPositionArr.begin(), m_aPositionArr.begin() + n ); + m_Boxes.erase(m_Boxes.begin(), m_Boxes.begin() + n); + + size_t nArrSize = m_aPositionArr.size(); + if (nArrSize) + { + if (nOldWidth == 0) + throw o3tl::divide_by_zero(); + + // Adapt the positions to the new Size + for( n = 0; n < nArrSize; ++n ) + { + sal_uLong nSize = m_nWidth; + nSize *= ( m_aPositionArr[ n ] - nOffset ); + nSize /= nOldWidth; + m_aPositionArr[ n ] = sal_uInt16( nSize ); + } + } + } + return !m_aPositionArr.empty(); +} + +bool sw_Line_CollectBox( const SwTableLine*& rpLine, void* pPara ) +{ + SwCollectTableLineBoxes* pSplPara = static_cast<SwCollectTableLineBoxes*>(pPara); + if( pSplPara->IsGetValues() ) + for( const auto& rpBox : const_cast<SwTableLine*>(rpLine)->GetTabBoxes() ) + sw_Box_CollectBox(rpBox, pSplPara ); + else + for( auto& rpBox : const_cast<SwTableLine*>(rpLine)->GetTabBoxes() ) + sw_BoxSetSplitBoxFormats(rpBox, pSplPara ); + return true; +} + +void sw_Box_CollectBox( const SwTableBox* pBox, SwCollectTableLineBoxes* pSplPara ) +{ + auto nLen = pBox->GetTabLines().size(); + if( nLen ) + { + // Continue with the actual Line + if( pSplPara->IsGetFromTop() ) + nLen = 0; + else + --nLen; + + const SwTableLine* pLn = pBox->GetTabLines()[ nLen ]; + sw_Line_CollectBox( pLn, pSplPara ); + } + else + pSplPara->AddBox( *pBox ); +} + +void sw_BoxSetSplitBoxFormats( SwTableBox* pBox, SwCollectTableLineBoxes* pSplPara ) +{ + auto nLen = pBox->GetTabLines().size(); + if( nLen ) + { + // Continue with the actual Line + if( pSplPara->IsGetFromTop() ) + nLen = 0; + else + --nLen; + + const SwTableLine* pLn = pBox->GetTabLines()[ nLen ]; + sw_Line_CollectBox( pLn, pSplPara ); + } + else + { + const SwTableBox* pSrcBox = pSplPara->GetBoxOfPos( *pBox ); + SwFrameFormat* pFormat = pSrcBox->GetFrameFormat(); + + if( SplitTable_HeadlineOption::BorderCopy == pSplPara->GetMode() ) + { + const SvxBoxItem& rBoxItem = pBox->GetFrameFormat()->GetBox(); + if( !rBoxItem.GetTop() ) + { + SvxBoxItem aNew( rBoxItem ); + aNew.SetLine( pFormat->GetBox().GetBottom(), SvxBoxItemLine::TOP ); + if( aNew != rBoxItem ) + pBox->ClaimFrameFormat()->SetFormatAttr( aNew ); + } + } + else + { + SfxItemSetFixed<RES_LR_SPACE, RES_UL_SPACE, + RES_PROTECT, RES_PROTECT, + RES_VERT_ORIENT, RES_VERT_ORIENT, + RES_BACKGROUND, RES_SHADOW> + aTmpSet( pFormat->GetDoc()->GetAttrPool() ); + aTmpSet.Put( pFormat->GetAttrSet() ); + if( aTmpSet.Count() ) + pBox->ClaimFrameFormat()->SetFormatAttr( aTmpSet ); + + if( SplitTable_HeadlineOption::BoxAttrAllCopy == pSplPara->GetMode() ) + { + SwNodeIndex aIdx( *pSrcBox->GetSttNd(), 1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = aIdx.GetNodes().GoNext( &aIdx ); + aIdx = *pBox->GetSttNd(); + SwContentNode* pDNd = aIdx.GetNodes().GoNext( &aIdx ); + + // If the Node is alone in the Section + if( SwNodeOffset(2) == pDNd->EndOfSectionIndex() - + pDNd->StartOfSectionIndex() ) + { + pSplPara->AddToUndoHistory( *pDNd ); + pDNd->ChgFormatColl( pCNd->GetFormatColl() ); + } + } + + // note conditional template + pBox->GetSttNd()->CheckSectionCondColl(); + } + } +} + +/** + * Splits a Table in the top-level Line which contains the Index. + * All succeeding top-level Lines go into a new Table/Node. + * + * @param bCalcNewSize true + * Calculate the new Size for both from the + * Boxes' Max; but only if Size is using absolute + * values (USHRT_MAX) + */ +void SwDoc::SplitTable( const SwPosition& rPos, SplitTable_HeadlineOption eHdlnMode, + bool bCalcNewSize ) +{ + SwNode* pNd = &rPos.nNode.GetNode(); + SwTableNode* pTNd = pNd->FindTableNode(); + if( !pTNd || pNd->IsTableNode() ) + return; + + if( dynamic_cast<const SwDDETable*>( &pTNd->GetTable() ) != nullptr) + return; + + SwTable& rTable = pTNd->GetTable(); + rTable.SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + SwTableFormulaUpdate aMsgHint( &rTable ); + + SwHistory aHistory; + if (GetIDocumentUndoRedo().DoesUndo()) + { + aMsgHint.m_pHistory = &aHistory; + } + + { + SwNodeOffset nSttIdx = pNd->FindTableBoxStartNode()->GetIndex(); + + // Find top-level Line + SwTableBox* pBox = rTable.GetTableBox( nSttIdx ); + if( pBox ) + { + SwTableLine* pLine = pBox->GetUpper(); + while( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + // pLine contains the top-level Line now + aMsgHint.m_nSplitLine = rTable.GetTabLines().GetPos( pLine ); + } + + OUString sNewTableNm( GetUniqueTableName() ); + aMsgHint.m_aData.pNewTableNm = &sNewTableNm; + aMsgHint.m_eFlags = TBL_SPLITTBL; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + } + + // Find Lines for the Layout update + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( rTable ); + aFndBox.DelFrames( rTable ); + + SwTableNode* pNew = GetNodes().SplitTable( rPos.nNode, false, bCalcNewSize ); + + if( pNew ) + { + std::unique_ptr<SwSaveRowSpan> pSaveRowSp = pNew->GetTable().CleanUpTopRowSpan( rTable.GetTabLines().size() ); + SwUndoSplitTable* pUndo = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo = new SwUndoSplitTable( + *pNew, std::move(pSaveRowSp), eHdlnMode, bCalcNewSize); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + if( aHistory.Count() ) + pUndo->SaveFormula( aHistory ); + } + + switch( eHdlnMode ) + { + // Set the lower Border of the preceding Line to + // the upper Border of the current one + case SplitTable_HeadlineOption::BorderCopy: + { + SwCollectTableLineBoxes aPara( false, eHdlnMode ); + SwTableLine* pLn = rTable.GetTabLines()[ + rTable.GetTabLines().size() - 1 ]; + for( const auto& rpBox : pLn->GetTabBoxes() ) + sw_Box_CollectBox(rpBox, &aPara ); + + aPara.SetValues( true ); + pLn = pNew->GetTable().GetTabLines()[ 0 ]; + for( auto& rpBox : pLn->GetTabBoxes() ) + sw_BoxSetSplitBoxFormats(rpBox, &aPara ); + + // Switch off repeating Header + pNew->GetTable().SetRowsToRepeat( 0 ); + } + break; + + // Take over the Attributes of the first Line to the new one + case SplitTable_HeadlineOption::BoxAttrCopy: + case SplitTable_HeadlineOption::BoxAttrAllCopy: + { + SwHistory* pHst = nullptr; + if( SplitTable_HeadlineOption::BoxAttrAllCopy == eHdlnMode && pUndo ) + pHst = pUndo->GetHistory(); + + SwCollectTableLineBoxes aPara( true, eHdlnMode, pHst ); + SwTableLine* pLn = rTable.GetTabLines()[ 0 ]; + for( const auto& rpBox : pLn->GetTabBoxes() ) + sw_Box_CollectBox(rpBox, &aPara ); + + aPara.SetValues( true ); + pLn = pNew->GetTable().GetTabLines()[ 0 ]; + for( auto& rpBox : pLn->GetTabBoxes() ) + sw_BoxSetSplitBoxFormats(rpBox, &aPara ); + } + break; + + case SplitTable_HeadlineOption::ContentCopy: + rTable.CopyHeadlineIntoTable( *pNew ); + if( pUndo ) + pUndo->SetTableNodeOffset( pNew->GetIndex() ); + break; + + case SplitTable_HeadlineOption::NONE: + // Switch off repeating the Header + pNew->GetTable().SetRowsToRepeat( 0 ); + break; + } + + // And insert Frames + SwNodeIndex aNdIdx( *pNew->EndOfSectionNode() ); + GetNodes().GoNext( &aNdIdx ); // To the next ContentNode + pNew->MakeOwnFrames( &aNdIdx ); + + // Insert a paragraph between the Table + GetNodes().MakeTextNode( SwNodeIndex( *pNew ), + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT ) ); + } + + // Update Layout + aFndBox.MakeFrames( rTable ); + + // TL_CHART2: need to inform chart of probably changed cell names + UpdateCharts( rTable.GetFrameFormat()->GetName() ); + + // update table style formatting of both the tables + if (SwFEShell* pFEShell = GetDocShell()->GetFEShell()) + { + pFEShell->UpdateTableStyleFormatting(pTNd); + pFEShell->UpdateTableStyleFormatting(pNew); + } + + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); +} + +static bool lcl_ChgTableSize( SwTable& rTable ) +{ + // The Attribute must not be set via the Modify or else all Boxes are + // set back to 0. + // So lock the Format. + SwFrameFormat* pFormat = rTable.GetFrameFormat(); + SwFormatFrameSize aTableMaxSz( pFormat->GetFrameSize() ); + + if( USHRT_MAX == aTableMaxSz.GetWidth() ) + return false; + + bool bLocked = pFormat->IsModifyLocked(); + pFormat->LockModify(); + + aTableMaxSz.SetWidth( 0 ); + + SwTableLines& rLns = rTable.GetTabLines(); + for( auto pLn : rLns ) + { + SwTwips nMaxLnWidth = 0; + SwTableBoxes& rBoxes = pLn->GetTabBoxes(); + for( auto pBox : rBoxes ) + nMaxLnWidth += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + + if( nMaxLnWidth > aTableMaxSz.GetWidth() ) + aTableMaxSz.SetWidth( nMaxLnWidth ); + } + pFormat->SetFormatAttr( aTableMaxSz ); + if( !bLocked ) // Release the Lock if appropriate + pFormat->UnlockModify(); + + return true; +} + +namespace { + +class SplitTable_Para +{ + std::map<SwFrameFormat const*, SwFrameFormat*> m_aSrcDestMap; + SwTableNode* m_pNewTableNode; + SwTable& m_rOldTable; + +public: + SplitTable_Para(SwTableNode* pNew, SwTable& rOld) + : m_pNewTableNode(pNew) + , m_rOldTable(rOld) + {} + SwFrameFormat* GetDestFormat( SwFrameFormat* pSrcFormat ) const + { + auto it = m_aSrcDestMap.find(pSrcFormat); + return it == m_aSrcDestMap.end() ? nullptr : it->second; + } + + void InsertSrcDest( SwFrameFormat const * pSrcFormat, SwFrameFormat* pDestFormat ) + { + m_aSrcDestMap[pSrcFormat] = pDestFormat; + } + + void ChgBox( SwTableBox* pBox ) + { + m_rOldTable.GetTabSortBoxes().erase(pBox); + m_pNewTableNode->GetTable().GetTabSortBoxes().insert(pBox); + } +}; + +} + +static void lcl_SplitTable_CpyBox( SwTableBox* pBox, SplitTable_Para* pPara ); + +static void lcl_SplitTable_CpyLine( SwTableLine* pLn, SplitTable_Para* pPara ) +{ + SwFrameFormat *pSrcFormat = pLn->GetFrameFormat(); + SwTableLineFormat* pDestFormat = static_cast<SwTableLineFormat*>( pPara->GetDestFormat( pSrcFormat ) ); + if( pDestFormat == nullptr ) + { + pPara->InsertSrcDest( pSrcFormat, pLn->ClaimFrameFormat() ); + } + else + pLn->ChgFrameFormat( pDestFormat ); + + for( auto& rpBox : pLn->GetTabBoxes() ) + lcl_SplitTable_CpyBox(rpBox, pPara ); +} + +static void lcl_SplitTable_CpyBox( SwTableBox* pBox, SplitTable_Para* pPara ) +{ + SwFrameFormat *pSrcFormat = pBox->GetFrameFormat(); + SwTableBoxFormat* pDestFormat = static_cast<SwTableBoxFormat*>(pPara->GetDestFormat( pSrcFormat )); + if( pDestFormat == nullptr ) + { + pPara->InsertSrcDest( pSrcFormat, pBox->ClaimFrameFormat() ); + } + else + pBox->ChgFrameFormat( pDestFormat ); + + if( pBox->GetSttNd() ) + pPara->ChgBox( pBox ); + else + for( SwTableLine* pLine : pBox->GetTabLines() ) + lcl_SplitTable_CpyLine( pLine, pPara ); +} + +SwTableNode* SwNodes::SplitTable( const SwNodeIndex& rPos, bool bAfter, + bool bCalcNewSize ) +{ + SwNode* pNd = &rPos.GetNode(); + SwTableNode* pTNd = pNd->FindTableNode(); + if( !pTNd || pNd->IsTableNode() ) + return nullptr; + + SwNodeOffset nSttIdx = pNd->FindTableBoxStartNode()->GetIndex(); + + // Find this Box/top-level line + SwTable& rTable = pTNd->GetTable(); + SwTableBox* pBox = rTable.GetTableBox( nSttIdx ); + if( !pBox ) + return nullptr; + + SwTableLine* pLine = pBox->GetUpper(); + while( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + // pLine now contains the top-level line + sal_uInt16 nLinePos = rTable.GetTabLines().GetPos( pLine ); + if( USHRT_MAX == nLinePos || + ( bAfter ? ++nLinePos >= rTable.GetTabLines().size() : !nLinePos )) + return nullptr; // Not found or last Line! + + // Find the first Box of the succeeding Line + SwTableLine* pNextLine = rTable.GetTabLines()[ nLinePos ]; + pBox = pNextLine->GetTabBoxes()[0]; + while( !pBox->GetSttNd() ) + pBox = pBox->GetTabLines()[0]->GetTabBoxes()[0]; + + // Insert an EndNode and TableNode into the Nodes Array + SwTableNode * pNewTableNd; + { + SwEndNode* pOldTableEndNd = pTNd->EndOfSectionNode()->GetEndNode(); + assert(pOldTableEndNd && "Where is the EndNode?"); + + SwNodeIndex aIdx( *pBox->GetSttNd() ); + new SwEndNode( aIdx, *pTNd ); + pNewTableNd = new SwTableNode( aIdx ); + pNewTableNd->GetTable().SetTableModel( rTable.IsNewModel() ); + + pOldTableEndNd->m_pStartOfSection = pNewTableNd; + pNewTableNd->m_pEndOfSection = pOldTableEndNd; + + SwNode* pBoxNd = aIdx.GetNode().GetStartNode(); + do { + OSL_ENSURE( pBoxNd->IsStartNode(), "This needs to be a StartNode!" ); + pBoxNd->m_pStartOfSection = pNewTableNd; + pBoxNd = (*this)[ pBoxNd->EndOfSectionIndex() + 1 ]; + } while( pBoxNd != pOldTableEndNd ); + } + + { + // Move the Lines + SwTable& rNewTable = pNewTableNd->GetTable(); + rNewTable.GetTabLines().insert( rNewTable.GetTabLines().begin(), + rTable.GetTabLines().begin() + nLinePos, rTable.GetTabLines().end() ); + + /* From the back (bottom right) to the front (top left) deregister all Boxes from the + Chart Data Provider. The Modify event is triggered in the calling function. + TL_CHART2: */ + SwChartDataProvider *pPCD = rTable.GetFrameFormat()->getIDocumentChartDataProviderAccess().GetChartDataProvider(); + if( pPCD ) + { + for (SwTableLines::size_type k = nLinePos; k < rTable.GetTabLines().size(); ++k) + { + const SwTableLines::size_type nLineIdx = (rTable.GetTabLines().size() - 1) - k + nLinePos; + const SwTableBoxes::size_type nBoxCnt = rTable.GetTabLines()[ nLineIdx ]->GetTabBoxes().size(); + for (SwTableBoxes::size_type j = 0; j < nBoxCnt; ++j) + { + const SwTableBoxes::size_type nIdx = nBoxCnt - 1 - j; + pPCD->DeleteBox( &rTable, *rTable.GetTabLines()[ nLineIdx ]->GetTabBoxes()[nIdx] ); + } + } + } + + // Delete + sal_uInt16 nDeleted = rTable.GetTabLines().size() - nLinePos; + rTable.GetTabLines().erase( rTable.GetTabLines().begin() + nLinePos, rTable.GetTabLines().end() ); + + // Move the affected Boxes. Make the Formats unique and correct the StartNodes + SplitTable_Para aPara( pNewTableNd, rTable ); + for( SwTableLine* pNewLine : rNewTable.GetTabLines() ) + lcl_SplitTable_CpyLine( pNewLine, &aPara ); + rTable.CleanUpBottomRowSpan( nDeleted ); + } + + { + // Copy the Table FrameFormat + SwFrameFormat* pOldTableFormat = rTable.GetFrameFormat(); + SwFrameFormat* pNewTableFormat = pOldTableFormat->GetDoc()->MakeTableFrameFormat( + pOldTableFormat->GetDoc()->GetUniqueTableName(), + pOldTableFormat->GetDoc()->GetDfltFrameFormat() ); + + *pNewTableFormat = *pOldTableFormat; + pNewTableNd->GetTable().RegisterToFormat( *pNewTableFormat ); + + pNewTableNd->GetTable().SetTableStyleName(rTable.GetTableStyleName()); + + // Calculate a new Size? + // lcl_ChgTableSize: Only execute the second call if the first call was + // successful, thus has an absolute Size + if( bCalcNewSize && lcl_ChgTableSize( rTable ) ) + lcl_ChgTableSize( pNewTableNd->GetTable() ); + } + + // TL_CHART2: need to inform chart of probably changed cell names + rTable.UpdateCharts(); + + return pNewTableNd; // That's it! +} + +/** + * rPos needs to be in the Table that remains + * + * @param bWithPrev merge the current Table with the preceding + * or succeeding one + */ +bool SwDoc::MergeTable( const SwPosition& rPos, bool bWithPrev, sal_uInt16 nMode ) +{ + SwTableNode* pTableNd = rPos.nNode.GetNode().FindTableNode(), *pDelTableNd; + if( !pTableNd ) + return false; + + SwNodes& rNds = GetNodes(); + if( bWithPrev ) + pDelTableNd = rNds[ pTableNd->GetIndex() - 1 ]->FindTableNode(); + else + pDelTableNd = rNds[ pTableNd->EndOfSectionIndex() + 1 ]->GetTableNode(); + if( !pDelTableNd ) + return false; + + if( dynamic_cast<const SwDDETable*>( &pTableNd->GetTable() ) != nullptr || + dynamic_cast<const SwDDETable*>( &pDelTableNd->GetTable() ) != nullptr) + return false; + + // Delete HTML Layout + pTableNd->GetTable().SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); + pDelTableNd->GetTable().SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); + + // Both Tables are present; we can start + SwUndoMergeTable* pUndo = nullptr; + std::unique_ptr<SwHistory> pHistory; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo = new SwUndoMergeTable( *pTableNd, *pDelTableNd, bWithPrev, nMode ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + pHistory.reset(new SwHistory); + } + + // Adapt all "TableFormulas" + SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() ); + aMsgHint.m_aData.pDelTable = &pDelTableNd->GetTable(); + aMsgHint.m_eFlags = TBL_MERGETBL; + aMsgHint.m_pHistory = pHistory.get(); + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + // The actual merge + SwNodeIndex aIdx( bWithPrev ? *pTableNd : *pDelTableNd ); + bool bRet = rNds.MergeTable( aIdx, !bWithPrev, nMode ); + + if( pHistory ) + { + if( pHistory->Count() ) + pUndo->SaveFormula( *pHistory ); + pHistory.reset(); + } + if( bRet ) + { + if (SwFEShell* pFEShell = GetDocShell()->GetFEShell()) + pFEShell->UpdateTableStyleFormatting(); + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + } + return bRet; +} + +bool SwNodes::MergeTable( const SwNodeIndex& rPos, bool bWithPrev, + sal_uInt16 nMode ) +{ + SwTableNode* pDelTableNd = rPos.GetNode().GetTableNode(); + OSL_ENSURE( pDelTableNd, "Where did the TableNode go?" ); + + SwTableNode* pTableNd = (*this)[ rPos.GetIndex() - 1]->FindTableNode(); + OSL_ENSURE( pTableNd, "Where did the TableNode go?" ); + + if( !pDelTableNd || !pTableNd ) + return false; + + pDelTableNd->DelFrames(); + + SwTable& rDelTable = pDelTableNd->GetTable(); + SwTable& rTable = pTableNd->GetTable(); + + // Find Lines for the Layout update + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( rTable ); + aFndBox.DelFrames( rTable ); + + // TL_CHART2: + // tell the charts about the table to be deleted and have them use their own data + GetDoc().getIDocumentChartDataProviderAccess().CreateChartInternalDataProviders( &rDelTable ); + + // Sync the TableFormat's Width + { + const SwFormatFrameSize& rTableSz = rTable.GetFrameFormat()->GetFrameSize(); + const SwFormatFrameSize& rDelTableSz = rDelTable.GetFrameFormat()->GetFrameSize(); + if( rTableSz != rDelTableSz ) + { + // The needs correction + if( bWithPrev ) + rDelTable.GetFrameFormat()->SetFormatAttr( rTableSz ); + else + rTable.GetFrameFormat()->SetFormatAttr( rDelTableSz ); + } + } + + if( !bWithPrev ) + { + // Transfer all Attributes of the succeeding Table to the preceding one + // We do this, because the succeeding one is deleted when deleting the Node + rTable.SetRowsToRepeat( rDelTable.GetRowsToRepeat() ); + rTable.SetTableChgMode( rDelTable.GetTableChgMode() ); + + rTable.GetFrameFormat()->LockModify(); + *rTable.GetFrameFormat() = *rDelTable.GetFrameFormat(); + // Also switch the Name + rTable.GetFrameFormat()->SetName( rDelTable.GetFrameFormat()->GetName() ); + rTable.GetFrameFormat()->UnlockModify(); + } + + // Move the Lines and Boxes + SwTableLines::size_type nOldSize = rTable.GetTabLines().size(); + rTable.GetTabLines().insert( rTable.GetTabLines().begin() + nOldSize, + rDelTable.GetTabLines().begin(), rDelTable.GetTabLines().end() ); + rDelTable.GetTabLines().clear(); + + rTable.GetTabSortBoxes().insert( rDelTable.GetTabSortBoxes() ); + rDelTable.GetTabSortBoxes().clear(); + + // The preceding Table always remains, while the succeeding one is deleted + SwEndNode* pTableEndNd = pDelTableNd->EndOfSectionNode(); + pTableNd->m_pEndOfSection = pTableEndNd; + + SwNodeIndex aIdx( *pDelTableNd, 1 ); + + SwNode* pBoxNd = aIdx.GetNode().GetStartNode(); + do { + OSL_ENSURE( pBoxNd->IsStartNode(), "This needs to be a StartNode!" ); + pBoxNd->m_pStartOfSection = pTableNd; + pBoxNd = (*this)[ pBoxNd->EndOfSectionIndex() + 1 ]; + } while( pBoxNd != pTableEndNd ); + pBoxNd->m_pStartOfSection = pTableNd; + + aIdx -= SwNodeOffset(2); + DelNodes( aIdx, SwNodeOffset(2) ); + + // tweak the conditional styles at the first inserted Line + const SwTableLine* pFirstLn = rTable.GetTabLines()[ nOldSize ]; + if( 1 == nMode ) + { + // Set Header Template in the Line and save in the History + // if needed for Undo! + } + sw_LineSetHeadCondColl( pFirstLn ); + + // Clean up the Borders + if( nOldSize ) + { + SwGCLineBorder aPara( rTable ); + aPara.nLinePos = --nOldSize; + pFirstLn = rTable.GetTabLines()[ nOldSize ]; + sw_GC_Line_Border( pFirstLn, &aPara ); + } + + // Update Layout + aFndBox.MakeFrames( rTable ); + + return true; +} + +namespace { + +// Use the PtrArray's ForEach method +struct SetAFormatTabPara +{ + SwTableAutoFormat& rTableFormat; + SwUndoTableAutoFormat* pUndo; + sal_uInt16 nEndBox, nCurBox; + sal_uInt8 nAFormatLine, nAFormatBox; + bool bSingleRowTable; + + explicit SetAFormatTabPara( const SwTableAutoFormat& rNew ) + : rTableFormat( const_cast<SwTableAutoFormat&>(rNew) ), pUndo( nullptr ), + nEndBox( 0 ), nCurBox( 0 ), nAFormatLine( 0 ), nAFormatBox( 0 ), bSingleRowTable(false) + {} +}; + +} + +// Forward declare so that the Lines and Boxes can use recursion +static bool lcl_SetAFormatBox(FndBox_ &, SetAFormatTabPara *pSetPara, bool bResetDirect); +static bool lcl_SetAFormatLine(FndLine_ &, SetAFormatTabPara *pPara, bool bResetDirect); + +static bool lcl_SetAFormatLine(FndLine_ & rLine, SetAFormatTabPara *pPara, bool bResetDirect) +{ + for (auto const& it : rLine.GetBoxes()) + { + lcl_SetAFormatBox(*it, pPara, bResetDirect); + } + return true; +} + +static bool lcl_SetAFormatBox(FndBox_ & rBox, SetAFormatTabPara *pSetPara, bool bResetDirect) +{ + if (!rBox.GetUpper()->GetUpper()) // Box on first level? + { + if( !pSetPara->nCurBox ) + pSetPara->nAFormatBox = 0; + else if( pSetPara->nCurBox == pSetPara->nEndBox ) + pSetPara->nAFormatBox = 3; + else //Even column(1) or Odd column(2) + pSetPara->nAFormatBox = static_cast<sal_uInt8>(1 + ((pSetPara->nCurBox-1) & 1)); + } + + if (rBox.GetBox()->GetSttNd()) + { + SwTableBox* pSetBox = rBox.GetBox(); + if (!pSetBox->HasDirectFormatting() || bResetDirect) + { + if (bResetDirect) + pSetBox->SetDirectFormatting(false); + + SwDoc* pDoc = pSetBox->GetFrameFormat()->GetDoc(); + SfxItemSetFixed<RES_CHRATR_BEGIN, RES_PARATR_LIST_END-1> aCharSet(pDoc->GetAttrPool()); + SfxItemSet aBoxSet(pDoc->GetAttrPool(), aTableBoxSetRange); + sal_uInt8 nPos = pSetPara->nAFormatLine * 4 + pSetPara->nAFormatBox; + const bool bSingleRowTable = pSetPara->bSingleRowTable; + const bool bSingleColTable = pSetPara->nEndBox == 0; + pSetPara->rTableFormat.UpdateToSet(nPos, bSingleRowTable, bSingleColTable, aCharSet, SwTableAutoFormatUpdateFlags::Char, nullptr); + pSetPara->rTableFormat.UpdateToSet(nPos, bSingleRowTable, bSingleColTable, aBoxSet, SwTableAutoFormatUpdateFlags::Box, pDoc->GetNumberFormatter()); + + if (aCharSet.Count()) + { + SwNodeOffset nSttNd = pSetBox->GetSttIdx()+1; + SwNodeOffset nEndNd = pSetBox->GetSttNd()->EndOfSectionIndex(); + for (; nSttNd < nEndNd; ++nSttNd) + { + SwContentNode* pNd = pDoc->GetNodes()[ nSttNd ]->GetContentNode(); + if (pNd) + pNd->SetAttr(aCharSet); + } + } + + if (aBoxSet.Count()) + { + if (pSetPara->pUndo && SfxItemState::SET == aBoxSet.GetItemState(RES_BOXATR_FORMAT)) + pSetPara->pUndo->SaveBoxContent( *pSetBox ); + + pSetBox->ClaimFrameFormat()->SetFormatAttr(aBoxSet); + } + } + } + else + { + // Not sure how this situation can occur, but apparently we have some kind of table in table. + // I am guessing at how to best handle singlerow in this situation. + const bool bOrigSingleRowTable = pSetPara->bSingleRowTable; + pSetPara->bSingleRowTable = rBox.GetLines().size() == 1; + for (auto const& rpFndLine : rBox.GetLines()) + { + lcl_SetAFormatLine(*rpFndLine, pSetPara, bResetDirect); + } + pSetPara->bSingleRowTable = bOrigSingleRowTable; + } + + if (!rBox.GetUpper()->GetUpper()) // a BaseLine + ++pSetPara->nCurBox; + return true; +} + +bool SwDoc::SetTableAutoFormat(const SwSelBoxes& rBoxes, const SwTableAutoFormat& rNew, bool bResetDirect, bool const isSetStyleName) +{ + OSL_ENSURE( !rBoxes.empty(), "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( rBoxes, &aFndBox ); + ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return false; + + SwTable &table = pTableNd->GetTable(); + table.SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); + + FndBox_* pFndBox = &aFndBox; + while( 1 == pFndBox->GetLines().size() && + 1 == pFndBox->GetLines().front()->GetBoxes().size()) + { + pFndBox = pFndBox->GetLines().front()->GetBoxes()[0].get(); + } + + if( pFndBox->GetLines().empty() ) // One too far? (only one sel. Box) + pFndBox = pFndBox->GetUpper()->GetUpper(); + + // Disable Undo, but first store parameters + SwUndoTableAutoFormat* pUndo = nullptr; + bool const bUndo(GetIDocumentUndoRedo().DoesUndo()); + if (bUndo) + { + pUndo = new SwUndoTableAutoFormat( *pTableNd, rNew ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + GetIDocumentUndoRedo().DoUndo(false); + } + + if (isSetStyleName) + { // tdf#98226 do this here where undo can record it + pTableNd->GetTable().SetTableStyleName(rNew.GetName()); + } + + rNew.RestoreTableProperties(table); + + SetAFormatTabPara aPara( rNew ); + FndLines_t& rFLns = pFndBox->GetLines(); + aPara.bSingleRowTable = rFLns.size() == 1; + + for (FndLines_t::size_type n = 0; n < rFLns.size(); ++n) + { + FndLine_* pLine = rFLns[n].get(); + + // Set Upper to 0 (thus simulate BaseLine) + FndBox_* pSaveBox = pLine->GetUpper(); + pLine->SetUpper( nullptr ); + + if( !n ) + aPara.nAFormatLine = 0; + else if (static_cast<size_t>(n+1) == rFLns.size()) + aPara.nAFormatLine = 3; + else + aPara.nAFormatLine = static_cast<sal_uInt8>(1 + ((n-1) & 1 )); + + aPara.nAFormatBox = 0; + aPara.nCurBox = 0; + aPara.nEndBox = pLine->GetBoxes().size()-1; + aPara.pUndo = pUndo; + for (auto const& it : pLine->GetBoxes()) + { + lcl_SetAFormatBox(*it, &aPara, bResetDirect); + } + + pLine->SetUpper( pSaveBox ); + } + + if( pUndo ) + { + GetIDocumentUndoRedo().DoUndo(bUndo); + } + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + + return true; +} + +/** + * Find out who has the Attributes + */ +bool SwDoc::GetTableAutoFormat( const SwSelBoxes& rBoxes, SwTableAutoFormat& rGet ) +{ + OSL_ENSURE( !rBoxes.empty(), "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( rBoxes, &aFndBox ); + ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return false; + + // Store table properties + SwTable &table = pTableNd->GetTable(); + rGet.StoreTableProperties(table); + + FndBox_* pFndBox = &aFndBox; + while( 1 == pFndBox->GetLines().size() && + 1 == pFndBox->GetLines().front()->GetBoxes().size()) + { + pFndBox = pFndBox->GetLines().front()->GetBoxes()[0].get(); + } + + if( pFndBox->GetLines().empty() ) // One too far? (only one sel. Box) + pFndBox = pFndBox->GetUpper()->GetUpper(); + + FndLines_t& rFLns = pFndBox->GetLines(); + + sal_uInt16 aLnArr[4]; + aLnArr[0] = 0; + aLnArr[1] = 1 < rFLns.size() ? 1 : 0; + aLnArr[2] = 2 < rFLns.size() ? 2 : aLnArr[1]; + aLnArr[3] = rFLns.size() - 1; + + for( sal_uInt8 nLine = 0; nLine < 4; ++nLine ) + { + FndLine_& rLine = *rFLns[ aLnArr[ nLine ] ]; + + sal_uInt16 aBoxArr[4]; + aBoxArr[0] = 0; + aBoxArr[1] = 1 < rLine.GetBoxes().size() ? 1 : 0; + aBoxArr[2] = 2 < rLine.GetBoxes().size() ? 2 : aBoxArr[1]; + aBoxArr[3] = rLine.GetBoxes().size() - 1; + + for( sal_uInt8 nBox = 0; nBox < 4; ++nBox ) + { + SwTableBox* pFBox = rLine.GetBoxes()[ aBoxArr[ nBox ] ]->GetBox(); + // Always apply to the first ones + while( !pFBox->GetSttNd() ) + pFBox = pFBox->GetTabLines()[0]->GetTabBoxes()[0]; + + sal_uInt8 nPos = nLine * 4 + nBox; + SwNodeIndex aIdx( *pFBox->GetSttNd(), 1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = GetNodes().GoNext( &aIdx ); + + if( pCNd ) + rGet.UpdateFromSet( nPos, pCNd->GetSwAttrSet(), + SwTableAutoFormatUpdateFlags::Char, nullptr ); + rGet.UpdateFromSet( nPos, pFBox->GetFrameFormat()->GetAttrSet(), + SwTableAutoFormatUpdateFlags::Box, + GetNumberFormatter() ); + } + } + + return true; +} + +SwTableAutoFormatTable& SwDoc::GetTableStyles() +{ + if (!m_pTableStyles) + { + m_pTableStyles.reset(new SwTableAutoFormatTable); + m_pTableStyles->Load(); + } + return *m_pTableStyles; +} + +OUString SwDoc::GetUniqueTableName() const +{ + if( IsInMailMerge()) + { + OUString newName = "MailMergeTable" + + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US ) + + OUString::number( mpTableFrameFormatTable->size() + 1 ); + return newName; + } + + const OUString aName(SwResId(STR_TABLE_DEFNAME)); + + const size_t nFlagSize = ( mpTableFrameFormatTable->size() / 8 ) + 2; + + std::unique_ptr<sal_uInt8[]> pSetFlags( new sal_uInt8[ nFlagSize ] ); + memset( pSetFlags.get(), 0, nFlagSize ); + + for( size_t n = 0; n < mpTableFrameFormatTable->size(); ++n ) + { + const SwFrameFormat* pFormat = (*mpTableFrameFormatTable)[ n ]; + if( !pFormat->IsDefault() && IsUsed( *pFormat ) && + pFormat->GetName().startsWith( aName ) ) + { + // Get number and set the Flag + const sal_Int32 nNmLen = aName.getLength(); + size_t nNum = o3tl::toInt32(pFormat->GetName().subView( nNmLen )); + if( nNum-- && nNum < mpTableFrameFormatTable->size() ) + pSetFlags[ nNum / 8 ] |= (0x01 << ( nNum & 0x07 )); + } + } + + // All numbers are flagged properly, thus calculate the right number + size_t nNum = mpTableFrameFormatTable->size(); + for( size_t n = 0; n < nFlagSize; ++n ) + { + auto nTmp = pSetFlags[ n ]; + if( nTmp != 0xFF ) + { + // Calculate the number + nNum = n * 8; + while( nTmp & 1 ) + { + ++nNum; + nTmp >>= 1; + } + break; + } + } + + return aName + OUString::number( ++nNum ); +} + +SwTableFormat* SwDoc::FindTableFormatByName( const OUString& rName, bool bAll ) const +{ + const SwFormat* pRet = nullptr; + if( bAll ) + pRet = mpTableFrameFormatTable->FindFormatByName( rName ); + else + { + auto [it, itEnd] = mpTableFrameFormatTable->findRangeByName(rName); + // Only the ones set in the Doc + for( ; it != itEnd; ++it ) + { + const SwFrameFormat* pFormat = *it; + if( !pFormat->IsDefault() && IsUsed( *pFormat ) && + pFormat->GetName() == rName ) + { + pRet = pFormat; + break; + } + } + } + return const_cast<SwTableFormat*>(static_cast<const SwTableFormat*>(pRet)); +} + +void SwDoc::SetColRowWidthHeight( SwTableBox& rCurrentBox, TableChgWidthHeightType eType, + SwTwips nAbsDiff, SwTwips nRelDiff ) +{ + SwTableNode* pTableNd = const_cast<SwTableNode*>(rCurrentBox.GetSttNd()->FindTableNode()); + std::unique_ptr<SwUndo> pUndo; + + SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() ); + aMsgHint.m_eFlags = TBL_BOXPTR; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + bool const bUndo(GetIDocumentUndoRedo().DoesUndo()); + bool bRet = false; + switch( extractPosition(eType) ) + { + case TableChgWidthHeightType::ColLeft: + case TableChgWidthHeightType::ColRight: + case TableChgWidthHeightType::CellLeft: + case TableChgWidthHeightType::CellRight: + { + bRet = pTableNd->GetTable().SetColWidth( rCurrentBox, + eType, nAbsDiff, nRelDiff, + bUndo ? &pUndo : nullptr ); + } + break; + case TableChgWidthHeightType::RowBottom: + case TableChgWidthHeightType::CellTop: + case TableChgWidthHeightType::CellBottom: + bRet = pTableNd->GetTable().SetRowHeight( rCurrentBox, + eType, nAbsDiff, nRelDiff, + bUndo ? &pUndo : nullptr ); + break; + default: break; + } + + GetIDocumentUndoRedo().DoUndo(bUndo); // SetColWidth can turn it off + if( pUndo ) + { + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + + if( bRet ) + { + getIDocumentState().SetModified(); + } +} + +bool SwDoc::IsNumberFormat( const OUString& rString, sal_uInt32& F_Index, double& fOutNumber ) +{ + if( rString.getLength() > 308 ) // optimization matches svl:IsNumberFormat arbitrary value + return false; + + // remove any comment anchor marks + OUStringBuffer sStringBuffer(rString); + sal_Int32 nCommentPosition = sStringBuffer.indexOf( CH_TXTATR_INWORD ); + while( nCommentPosition != -1 ) + { + sStringBuffer.remove( nCommentPosition, 1 ); + nCommentPosition = sStringBuffer.indexOf( CH_TXTATR_INWORD, nCommentPosition ); + } + + return GetNumberFormatter()->IsNumberFormat( sStringBuffer.makeStringAndClear(), F_Index, fOutNumber ); +} + +void SwDoc::ChkBoxNumFormat( SwTableBox& rBox, bool bCallUpdate ) +{ + // Optimization: If the Box says it's Text, it remains Text + const SwTableBoxNumFormat* pNumFormatItem = rBox.GetFrameFormat()->GetItemIfSet( RES_BOXATR_FORMAT, + false ); + if( pNumFormatItem && GetNumberFormatter()->IsTextFormat(pNumFormatItem->GetValue()) ) + return ; + + std::unique_ptr<SwUndoTableNumFormat> pUndo; + + bool bIsEmptyTextNd; + bool bChgd = true; + sal_uInt32 nFormatIdx; + double fNumber; + if( rBox.HasNumContent( fNumber, nFormatIdx, bIsEmptyTextNd ) ) + { + if( !rBox.IsNumberChanged() ) + bChgd = false; + else + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_AUTOFMT, nullptr ); + pUndo.reset(new SwUndoTableNumFormat( rBox )); + pUndo->SetNumFormat( nFormatIdx, fNumber ); + } + + SwTableBoxFormat* pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.GetFrameFormat()); + SfxItemSetFixed<RES_BOXATR_FORMAT, RES_BOXATR_VALUE> aBoxSet( GetAttrPool() ); + + bool bLockModify = true; + bool bSetNumberFormat = IsInsTableFormatNum(); + const bool bForceNumberFormat = IsInsTableFormatNum() && IsInsTableChangeNumFormat(); + + // if the user forced a number format in this cell previously, + // keep it, unless the user set that she wants the full number + // format recognition + if( pNumFormatItem && !bForceNumberFormat ) + { + sal_uLong nOldNumFormat = pNumFormatItem->GetValue(); + SvNumberFormatter* pNumFormatr = GetNumberFormatter(); + + SvNumFormatType nFormatType = pNumFormatr->GetType( nFormatIdx ); + if( nFormatType == pNumFormatr->GetType( nOldNumFormat ) || SvNumFormatType::NUMBER == nFormatType ) + { + // Current and specified NumFormat match + // -> keep old Format + nFormatIdx = nOldNumFormat; + bSetNumberFormat = true; + } + else + { + // Current and specified NumFormat do not match + // -> insert as Text + bLockModify = bSetNumberFormat = false; + } + } + + if( bSetNumberFormat || bForceNumberFormat ) + { + pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.ClaimFrameFormat()); + + aBoxSet.Put( SwTableBoxValue( fNumber )); + aBoxSet.Put( SwTableBoxNumFormat( nFormatIdx )); + } + + // It's not enough to only reset the Formula. + // Make sure that the Text is formatted accordingly + if( !bSetNumberFormat && !bIsEmptyTextNd && pNumFormatItem ) + { + // Just resetting Attributes is not enough + // Make sure that the Text is formatted accordingly + pBoxFormat->SetFormatAttr( *GetDfltAttr( RES_BOXATR_FORMAT )); + } + + if( bLockModify ) pBoxFormat->LockModify(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ); + if( bLockModify ) pBoxFormat->UnlockModify(); + + if( bSetNumberFormat ) + pBoxFormat->SetFormatAttr( aBoxSet ); + } + } + else + { + // It's not a number + SwTableBoxFormat* pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.GetFrameFormat()); + if( SfxItemState::SET == pBoxFormat->GetItemState( RES_BOXATR_FORMAT, false ) || + SfxItemState::SET == pBoxFormat->GetItemState( RES_BOXATR_VALUE, false ) ) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_AUTOFMT, nullptr ); + pUndo.reset(new SwUndoTableNumFormat( rBox )); + } + + pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.ClaimFrameFormat()); + + // Remove all number formats + sal_uInt16 nWhich1 = RES_BOXATR_FORMULA; + if( !bIsEmptyTextNd ) + { + nWhich1 = RES_BOXATR_FORMAT; + + // Just resetting Attributes is not enough + // Make sure that the Text is formatted accordingly + pBoxFormat->SetFormatAttr( *GetDfltAttr( nWhich1 )); + } + pBoxFormat->ResetFormatAttr( nWhich1, RES_BOXATR_VALUE ); + } + else + bChgd = false; + } + + if( !bChgd ) + return; + + if( pUndo ) + { + pUndo->SetBox( rBox ); + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + + const SwTableNode* pTableNd = rBox.GetSttNd()->FindTableNode(); + if( bCallUpdate ) + { + SwTableFormulaUpdate aTableUpdate( &pTableNd->GetTable() ); + getIDocumentFieldsAccess().UpdateTableFields( &aTableUpdate ); + + // TL_CHART2: update charts (when cursor leaves cell and + // automatic update is enabled) + if (AUTOUPD_FIELD_AND_CHARTS == GetDocumentSettingManager().getFieldUpdateFlags(true)) + pTableNd->GetTable().UpdateCharts(); + } + getIDocumentState().SetModified(); +} + +void SwDoc::SetTableBoxFormulaAttrs( SwTableBox& rBox, const SfxItemSet& rSet ) +{ + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoTableNumFormat>(rBox, &rSet) ); + } + + SwFrameFormat* pBoxFormat = rBox.ClaimFrameFormat(); + if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMULA )) + { + pBoxFormat->LockModify(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_VALUE ); + pBoxFormat->UnlockModify(); + } + else if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_VALUE )) + { + pBoxFormat->LockModify(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMULA ); + pBoxFormat->UnlockModify(); + } + pBoxFormat->SetFormatAttr( rSet ); + getIDocumentState().SetModified(); +} + +void SwDoc::ClearLineNumAttrs( SwPosition const & rPos ) +{ + SwPaM aPam(rPos); + aPam.Move(fnMoveBackward); + SwContentNode *pNode = aPam.GetContentNode(); + if ( nullptr == pNode ) + return ; + if( !pNode->IsTextNode() ) + return; + + SwTextNode * pTextNode = pNode->GetTextNode(); + if (!(pTextNode && pTextNode->IsNumbered() + && pTextNode->GetText().isEmpty())) + return; + + SfxItemSetFixed<RES_PARATR_BEGIN, RES_PARATR_END - 1> + rSet( pTextNode->GetDoc().GetAttrPool() ); + pTextNode->SwContentNode::GetAttr( rSet ); + const SfxStringItem* pFormatItem = rSet.GetItemIfSet( RES_PARATR_NUMRULE, false ); + if ( !pFormatItem ) + return; + + SwUndoDelNum * pUndo; + if( GetIDocumentUndoRedo().DoesUndo() ) + { + GetIDocumentUndoRedo().ClearRedo(); + pUndo = new SwUndoDelNum( aPam ); + GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + } + else + pUndo = nullptr; + SwRegHistory aRegH( pUndo ? pUndo->GetHistory() : nullptr ); + aRegH.RegisterInModify( pTextNode , *pTextNode ); + if ( pUndo ) + pUndo->AddNode( *pTextNode ); + std::unique_ptr<SfxStringItem> pNewItem(pFormatItem->Clone()); + pNewItem->SetValue(OUString()); + rSet.Put( std::move(pNewItem) ); + pTextNode->SetAttr( rSet ); +} + +void SwDoc::ClearBoxNumAttrs( const SwNodeIndex& rNode ) +{ + SwStartNode* pSttNd = rNode.GetNode().FindSttNodeByType( SwTableBoxStartNode ); + if( nullptr == pSttNd || + SwNodeOffset(2) != pSttNd->EndOfSectionIndex() - pSttNd->GetIndex()) + return; + + SwTableBox* pBox = pSttNd->FindTableNode()->GetTable(). + GetTableBox( pSttNd->GetIndex() ); + + const SfxItemSet& rSet = pBox->GetFrameFormat()->GetAttrSet(); + const SwTableBoxNumFormat* pFormatItem = rSet.GetItemIfSet( RES_BOXATR_FORMAT, false ); + if( !pFormatItem || + SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMULA, false ) || + SfxItemState::SET == rSet.GetItemState( RES_BOXATR_VALUE, false )) + return; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoTableNumFormat>(*pBox)); + } + + SwFrameFormat* pBoxFormat = pBox->ClaimFrameFormat(); + + // Keep TextFormats! + sal_uInt16 nWhich1 = RES_BOXATR_FORMAT; + if( pFormatItem && GetNumberFormatter()->IsTextFormat( + pFormatItem->GetValue() )) + nWhich1 = RES_BOXATR_FORMULA; + else + // Just resetting Attributes is not enough + // Make sure that the Text is formatted accordingly + pBoxFormat->SetFormatAttr( *GetDfltAttr( RES_BOXATR_FORMAT )); + + pBoxFormat->ResetFormatAttr( nWhich1, RES_BOXATR_VALUE ); + getIDocumentState().SetModified(); +} + +/** + * Copies a Table from the same or another Doc into itself + * We create a new Table or an existing one is filled with the Content. + * We either fill in the Content from a certain Box or a certain TableSelection + * + * This method is called by edglss.cxx/fecopy.cxx + */ +bool SwDoc::InsCopyOfTable( SwPosition& rInsPos, const SwSelBoxes& rBoxes, + const SwTable* pCpyTable, bool bCpyName, bool bCorrPos, const OUString& rStyleName ) +{ + bool bRet; + + const SwTableNode* pSrcTableNd = pCpyTable + ? pCpyTable->GetTableNode() + : rBoxes[ 0 ]->GetSttNd()->FindTableNode(); + + SwTableNode * pInsTableNd = rInsPos.nNode.GetNode().FindTableNode(); + + bool const bUndo( GetIDocumentUndoRedo().DoesUndo() ); + if( !pCpyTable && !pInsTableNd ) + { + std::unique_ptr<SwUndoCpyTable> pUndo; + if (bUndo) + { + GetIDocumentUndoRedo().ClearRedo(); + pUndo.reset(new SwUndoCpyTable(*this)); + } + + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + bRet = pSrcTableNd->GetTable().MakeCopy( *this, rInsPos, rBoxes, + bCpyName, rStyleName ); + } + + if( pUndo && bRet ) + { + pInsTableNd = GetNodes()[ rInsPos.nNode.GetIndex() - 1 ]->FindTableNode(); + + pUndo->SetTableSttIdx( pInsTableNd->GetIndex() ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + } + else + { + RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags(); + if( getIDocumentRedlineAccess().IsRedlineOn() ) + getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::On | + RedlineFlags::ShowInsert | + RedlineFlags::ShowDelete ); + + std::unique_ptr<SwUndoTableCpyTable> pUndo; + if (bUndo) + { + GetIDocumentUndoRedo().ClearRedo(); + pUndo.reset(new SwUndoTableCpyTable(*this)); + GetIDocumentUndoRedo().DoUndo(false); + } + + rtl::Reference<SwDoc> xCpyDoc(&const_cast<SwDoc&>(pSrcTableNd->GetDoc())); + bool bDelCpyDoc = xCpyDoc == this; + + if( bDelCpyDoc ) + { + // Copy the Table into a temporary Doc + xCpyDoc = new SwDoc; + + SwPosition aPos( SwNodeIndex( xCpyDoc->GetNodes().GetEndOfContent() )); + if( !pSrcTableNd->GetTable().MakeCopy( *xCpyDoc, aPos, rBoxes, true )) + { + xCpyDoc.clear(); + + if( pUndo ) + { + GetIDocumentUndoRedo().DoUndo(bUndo); + } + return false; + } + aPos.nNode -= SwNodeOffset(1); // Set to the Table's EndNode + pSrcTableNd = aPos.nNode.GetNode().FindTableNode(); + } + + const SwStartNode* pSttNd = rInsPos.nNode.GetNode().FindTableBoxStartNode(); + + rInsPos.nContent.Assign( nullptr, 0 ); + + // no complex into complex, but copy into or from new model is welcome + if( ( !pSrcTableNd->GetTable().IsTableComplex() || pInsTableNd->GetTable().IsNewModel() ) + && ( bDelCpyDoc || !rBoxes.empty() ) ) + { + // Copy the Table "relatively" + const SwSelBoxes* pBoxes; + SwSelBoxes aBoxes; + + if( bDelCpyDoc ) + { + SwTableBox* pBox = pInsTableNd->GetTable().GetTableBox( + pSttNd->GetIndex() ); + OSL_ENSURE( pBox, "Box is not in this Table" ); + aBoxes.insert( pBox ); + pBoxes = &aBoxes; + } + else + pBoxes = &rBoxes; + + // Copy Table to the selected Lines + bRet = pInsTableNd->GetTable().InsTable( pSrcTableNd->GetTable(), + *pBoxes, pUndo.get() ); + } + else + { + SwNodeIndex aNdIdx( *pSttNd, 1 ); + bRet = pInsTableNd->GetTable().InsTable( pSrcTableNd->GetTable(), + aNdIdx, pUndo.get() ); + } + + xCpyDoc.clear(); + + if( pUndo ) + { + // If the Table could not be copied, delete the Undo object + GetIDocumentUndoRedo().DoUndo(bUndo); + if( bRet || !pUndo->IsEmpty() ) + { + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + } + + if( bCorrPos ) + { + rInsPos.nNode = *pSttNd; + rInsPos.nContent.Assign( GetNodes().GoNext( &rInsPos.nNode ), 0 ); + } + getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + } + + if( bRet ) + { + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + } + return bRet; +} + +bool SwDoc::UnProtectTableCells( SwTable& rTable ) +{ + bool bChgd = false; + std::unique_ptr<SwUndoAttrTable> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoAttrTable( *rTable.GetTableNode() )); + + SwTableSortBoxes& rSrtBox = rTable.GetTabSortBoxes(); + for (size_t i = rSrtBox.size(); i; ) + { + SwFrameFormat *pBoxFormat = rSrtBox[ --i ]->GetFrameFormat(); + if( pBoxFormat->GetProtect().IsContentProtected() ) + { + pBoxFormat->ResetFormatAttr( RES_PROTECT ); + bChgd = true; + } + } + + if( pUndo && bChgd ) + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + return bChgd; +} + +void SwDoc::UnProtectCells( const OUString& rName ) +{ + SwTableFormat* pFormat = FindTableFormatByName( rName ); + if( pFormat ) + { + bool bChgd = UnProtectTableCells( *SwTable::FindTable( pFormat ) ); + if( bChgd ) + getIDocumentState().SetModified(); + } +} + +bool SwDoc::UnProtectCells( const SwSelBoxes& rBoxes ) +{ + bool bChgd = false; + if( !rBoxes.empty() ) + { + std::unique_ptr<SwUndoAttrTable> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoAttrTable( *rBoxes[0]->GetSttNd()->FindTableNode() )); + + std::map<SwFrameFormat*, SwTableBoxFormat*> aFormatsMap; + for (size_t i = rBoxes.size(); i; ) + { + SwTableBox* pBox = rBoxes[ --i ]; + SwFrameFormat* pBoxFormat = pBox->GetFrameFormat(); + if( pBoxFormat->GetProtect().IsContentProtected() ) + { + std::map<SwFrameFormat*, SwTableBoxFormat*>::const_iterator const it = + aFormatsMap.find(pBoxFormat); + if (aFormatsMap.end() != it) + pBox->ChgFrameFormat(it->second); + else + { + SwTableBoxFormat *const pNewBoxFormat( + static_cast<SwTableBoxFormat*>(pBox->ClaimFrameFormat())); + pNewBoxFormat->ResetFormatAttr( RES_PROTECT ); + aFormatsMap.insert(std::make_pair(pBoxFormat, pNewBoxFormat)); + } + bChgd = true; + } + } + + if( pUndo && bChgd ) + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + return bChgd; +} + +void SwDoc::UnProtectTables( const SwPaM& rPam ) +{ + GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + bool bChgd = false, bHasSel = rPam.HasMark() || + rPam.GetNext() != &rPam; + SwFrameFormats& rFormats = *GetTableFrameFormats(); + SwTable* pTable; + const SwTableNode* pTableNd; + for( auto n = rFormats.size(); n ; ) + if( nullptr != (pTable = SwTable::FindTable( rFormats[ --n ] )) && + nullptr != (pTableNd = pTable->GetTableNode() ) && + pTableNd->GetNodes().IsDocNodes() ) + { + SwNodeOffset nTableIdx = pTableNd->GetIndex(); + + // Check whether the Table is within the Selection + if( bHasSel ) + { + bool bFound = false; + SwPaM* pTmp = const_cast<SwPaM*>(&rPam); + do { + const SwPosition *pStt = pTmp->Start(), + *pEnd = pTmp->End(); + bFound = pStt->nNode.GetIndex() < nTableIdx && + nTableIdx < pEnd->nNode.GetIndex(); + + } while( !bFound && &rPam != ( pTmp = pTmp->GetNext() ) ); + if( !bFound ) + continue; // Continue searching + } + + // Lift the protection + bChgd |= UnProtectTableCells( *pTable ); + } + + GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + if( bChgd ) + getIDocumentState().SetModified(); +} + +bool SwDoc::HasTableAnyProtection( const SwPosition* pPos, + const OUString* pTableName, + bool* pFullTableProtection ) +{ + bool bHasProtection = false; + SwTable* pTable = nullptr; + if( pTableName ) + pTable = SwTable::FindTable( FindTableFormatByName( *pTableName ) ); + else if( pPos ) + { + SwTableNode* pTableNd = pPos->nNode.GetNode().FindTableNode(); + if( pTableNd ) + pTable = &pTableNd->GetTable(); + } + + if( pTable ) + { + SwTableSortBoxes& rSrtBox = pTable->GetTabSortBoxes(); + for (size_t i = rSrtBox.size(); i; ) + { + SwFrameFormat *pBoxFormat = rSrtBox[ --i ]->GetFrameFormat(); + if( pBoxFormat->GetProtect().IsContentProtected() ) + { + if( !bHasProtection ) + { + bHasProtection = true; + if( !pFullTableProtection ) + break; + *pFullTableProtection = true; + } + } + else if( bHasProtection && pFullTableProtection ) + { + *pFullTableProtection = false; + break; + } + } + } + return bHasProtection; +} + +SwTableAutoFormat* SwDoc::MakeTableStyle(const OUString& rName, bool bBroadcast) +{ + SwTableAutoFormat aTableFormat(rName); + GetTableStyles().AddAutoFormat(aTableFormat); + SwTableAutoFormat* pTableFormat = GetTableStyles().FindAutoFormat(rName); + + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoTableStyleMake>(rName, *this)); + } + + if (bBroadcast) + BroadcastStyleOperation(rName, SfxStyleFamily::Table, SfxHintId::StyleSheetCreated); + + return pTableFormat; +} + +std::unique_ptr<SwTableAutoFormat> SwDoc::DelTableStyle(const OUString& rName, bool bBroadcast) +{ + if (bBroadcast) + BroadcastStyleOperation(rName, SfxStyleFamily::Table, SfxHintId::StyleSheetErased); + + std::unique_ptr<SwTableAutoFormat> pReleasedFormat = GetTableStyles().ReleaseAutoFormat(rName); + + std::vector<SwTable*> vAffectedTables; + if (pReleasedFormat) + { + size_t nTableCount = GetTableFrameFormatCount(true); + for (size_t i=0; i < nTableCount; ++i) + { + SwFrameFormat* pFrameFormat = &GetTableFrameFormat(i, true); + SwTable* pTable = SwTable::FindTable(pFrameFormat); + if (pTable->GetTableStyleName() == pReleasedFormat->GetName()) + { + pTable->SetTableStyleName(""); + vAffectedTables.push_back(pTable); + } + } + + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoTableStyleDelete>(std::move(pReleasedFormat), std::move(vAffectedTables), *this)); + } + } + + return pReleasedFormat; +} + +void SwDoc::ChgTableStyle(const OUString& rName, const SwTableAutoFormat& rNewFormat) +{ + SwTableAutoFormat* pFormat = GetTableStyles().FindAutoFormat(rName); + if (!pFormat) + return; + + SwTableAutoFormat aOldFormat = *pFormat; + *pFormat = rNewFormat; + pFormat->SetName(rName); + + size_t nTableCount = GetTableFrameFormatCount(true); + for (size_t i=0; i < nTableCount; ++i) + { + SwFrameFormat* pFrameFormat = &GetTableFrameFormat(i, true); + SwTable* pTable = SwTable::FindTable(pFrameFormat); + if (pTable->GetTableStyleName() == rName) + if (SwFEShell* pFEShell = GetDocShell()->GetFEShell()) + pFEShell->UpdateTableStyleFormatting(pTable->GetTableNode()); + } + + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoTableStyleUpdate>(*pFormat, aOldFormat, *this)); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |