/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 DfltBoxAttrMap_t; typedef std::vector 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(static_cast(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 &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 &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(static_cast(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 *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(nCols + ( text::HoriOrientation::NONE == eAdjust ? 2 : 1 )) != pColArr->size() ) pColArr = nullptr; } OUString aTableName = GetUniqueTableName(); if( GetIDocumentUndoRedo().DoesUndo() ) { GetIDocumentUndoRedo().AppendUndo( std::make_unique( rPos, nCols, nRows, o3tl::narrowing(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 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 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(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(eAdjust), pTAFormat ); GetIDocumentUndoRedo().AppendUndo( std::unique_ptr(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(pEnd->nNode); const_cast(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 > aBoxFormatArr2; if( bUseBoxFormat ) { aBoxFormatArr1.reset(new DfltBoxAttrList_t( nBoxArrLen, nullptr )); } else { aBoxFormatArr2 = std::vector( nBoxArrLen, nullptr ); } SfxItemSetFixed 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(!n ? 0 : (( n+1 == nRows ) ? 12 : (4 * (1 + ((n-1) & 1 ))))); nId = nId + static_cast(!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(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 *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 *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 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(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( aFInfo.GetCharPos(nPos+TextFrameIndex(1), false)) ); } } aPosArr.push_back( o3tl::narrowing(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 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 >& rTableNodes ) { if (rTableNodes.empty()) return nullptr; const std::vector& rFirstRange = *rTableNodes.begin(); if (rFirstRange.empty()) return nullptr; const std::vector& 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(pEnd->nNode); const_cast(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 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 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(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 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 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(pFrameNd)->MakeFramesForAdjacentContentNode(*pCNd); else if( pFrameNd->IsTableNode() ) static_cast(pFrameNd)->MakeFramesForAdjacentContentNode(aDelRg.aStart); else if( pFrameNd->IsSectionNode() ) static_cast(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(rBoxes[0]->GetSttNd()->FindTableNode()); if( !pTableNd ) return false; SwTable& rTable = pTableNd->GetTable(); if( dynamic_cast( &rTable) != nullptr) return false; SwTableSortBoxes aTmpLst; std::unique_ptr 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(rBoxes[0]->GetSttNd()->FindTableNode()); if( !pTableNd ) return false; SwTable& rTable = pTableNd->GetTable(); if( dynamic_cast( &rTable) != nullptr) return false; SwTableSortBoxes aTmpLst; std::unique_ptr 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( & 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(static_cast(&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(rBoxes[0]->GetSttNd()->FindTableNode()); if( !pTableNd ) return false; if (!(eMode & SwDoc::RowColMode::DeleteProtected) && dynamic_cast(&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 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 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(rBoxes[0]->GetSttNd()->FindTableNode()); if( !pTableNd ) return false; SwTable& rTable = pTableNd->GetTable(); if( dynamic_cast( &rTable) != nullptr) return false; std::vector aNdsCnts; SwTableSortBoxes aTmpLst; std::unique_ptr 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( &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 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( GetUndoManager().RemoveLastUndo()); if (pU && pU->GetRedlSaveCount()) { SwEditShell *const pEditShell(GetEditShell()); assert(pEditShell); ::sw::UndoRedoContext context(*this, *pEditShell); static_cast(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(i) ) = *rPam.GetPoint(); if (SwTableCursor* pTableCursor = dynamic_cast(&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 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 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(static_cast(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(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(pFrame), aBoxes ); else { if ( aHidden[ nTmpVal ] && lcl_IsFrameInColumn( *static_cast(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(static_cast(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(static_cast(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(*pFrame) ); if ( pContent && pContent->IsTextFrame() ) { const SwTableBox* pBox = static_cast(pFrame)->GetTabBox(); const sal_Int32 nRowSpan = pBox->getRowSpan(); if( nRowSpan > 0 ) // Not overlapped pTextFrame = static_cast(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(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( *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(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(&rBox); m_Boxes.push_back(p); m_nWidth = m_nWidth + o3tl::narrowing(rBox.GetFrameFormat()->GetFrameSize().GetWidth()); } const SwTableBox* SwCollectTableLineBoxes::GetBoxOfPos( const SwTableBox& rBox ) { const SwTableBox* pRet = nullptr; if( !m_aPositionArr.empty() ) { std::vector::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(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::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(pPara); if( pSplPara->IsGetValues() ) for( const auto& rpBox : const_cast(rpLine)->GetTabBoxes() ) sw_Box_CollectBox(rpBox, pSplPara ); else for( auto& rpBox : const_cast(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 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( &pTNd->GetTable() ) != nullptr) return; SwTable& rTable = pTNd->GetTable(); rTable.SetHTMLTableLayout(std::shared_ptr()); // 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 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(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 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( 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(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( &pTableNd->GetTable() ) != nullptr || dynamic_cast( &pDelTableNd->GetTable() ) != nullptr) return false; // Delete HTML Layout pTableNd->GetTable().SetHTMLTableLayout(std::shared_ptr()); pDelTableNd->GetTable().SetHTMLTableLayout(std::shared_ptr()); // Both Tables are present; we can start SwUndoMergeTable* pUndo = nullptr; std::unique_ptr pHistory; if (GetIDocumentUndoRedo().DoesUndo()) { pUndo = new SwUndoMergeTable( *pTableNd, *pDelTableNd, bWithPrev, nMode ); GetIDocumentUndoRedo().AppendUndo(std::unique_ptr(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(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(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 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(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()); 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(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(n+1) == rFLns.size()) aPara.nAFormatLine = 3; else aPara.nAFormatLine = static_cast(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(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 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(static_cast(pRet)); } void SwDoc::SetColRowWidthHeight( SwTableBox& rCurrentBox, TableChgWidthHeightType eType, SwTwips nAbsDiff, SwTwips nRelDiff ) { SwTableNode* pTableNd = const_cast(rCurrentBox.GetSttNd()->FindTableNode()); std::unique_ptr 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 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(rBox.GetFrameFormat()); SfxItemSetFixed 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(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(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(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(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 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(pUndo) ); } else pUndo = nullptr; SwRegHistory aRegH( pUndo ? pUndo->GetHistory() : nullptr ); aRegH.RegisterInModify( pTextNode , *pTextNode ); if ( pUndo ) pUndo->AddNode( *pTextNode ); std::unique_ptr 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(*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 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 pUndo; if (bUndo) { GetIDocumentUndoRedo().ClearRedo(); pUndo.reset(new SwUndoTableCpyTable(*this)); GetIDocumentUndoRedo().DoUndo(false); } rtl::Reference xCpyDoc(&const_cast(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 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 pUndo; if (GetIDocumentUndoRedo().DoesUndo()) pUndo.reset(new SwUndoAttrTable( *rBoxes[0]->GetSttNd()->FindTableNode() )); std::map aFormatsMap; for (size_t i = rBoxes.size(); i; ) { SwTableBox* pBox = rBoxes[ --i ]; SwFrameFormat* pBoxFormat = pBox->GetFrameFormat(); if( pBoxFormat->GetProtect().IsContentProtected() ) { std::map::const_iterator const it = aFormatsMap.find(pBoxFormat); if (aFormatsMap.end() != it) pBox->ChgFrameFormat(it->second); else { SwTableBoxFormat *const pNewBoxFormat( static_cast(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(&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(rName, *this)); } if (bBroadcast) BroadcastStyleOperation(rName, SfxStyleFamily::Table, SfxHintId::StyleSheetCreated); return pTableFormat; } std::unique_ptr SwDoc::DelTableStyle(const OUString& rName, bool bBroadcast) { if (bBroadcast) BroadcastStyleOperation(rName, SfxStyleFamily::Table, SfxHintId::StyleSheetErased); std::unique_ptr pReleasedFormat = GetTableStyles().ReleaseAutoFormat(rName); std::vector 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(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(*pFormat, aOldFormat, *this)); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */