/* -*- 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 using ::editeng::SvxBorderLine; using namespace ::com::sun::star; // See swtable.cxx too #define COLFUZZY 20L static bool IsSame( tools::Long nA, tools::Long nB ) { return std::abs(nA-nB) <= COLFUZZY; } namespace { // SwTableLine::ChgFrameFormat may delete old format which doesn't have writer listeners anymore. // This may invalidate my pointers, and lead to use-after-free. For this reason, I register myself // as a writer listener for the old format here, and take care to delete formats without listeners // in my own dtor. class SwTableFormatCmp : public SwClient { public: SwTableFormatCmp( SwFrameFormat *pOld, SwFrameFormat *pNew, sal_Int16 nType ); ~SwTableFormatCmp() override; static SwFrameFormat* FindNewFormat(std::vector>& rArr, SwFrameFormat const* pOld, sal_Int16 nType); private: SwFrameFormat *m_pOld, *m_pNew; sal_Int16 m_nType; }; } SwTableFormatCmp::SwTableFormatCmp(SwFrameFormat* pO, SwFrameFormat* pN, sal_Int16 nT) : m_pOld(pO) , m_pNew(pN) , m_nType(nT) { if (m_pOld) m_pOld->Add(this); } SwTableFormatCmp::~SwTableFormatCmp() { if (m_pOld) { m_pOld->Remove(this); if (!m_pOld->HasWriterListeners()) delete m_pOld; } } // static SwFrameFormat* SwTableFormatCmp::FindNewFormat(std::vector>& rArr, SwFrameFormat const* pOld, sal_Int16 nType) { for (const auto& pCmp : rArr) { if (pCmp->m_pOld == pOld && pCmp->m_nType == nType) return pCmp->m_pNew; } return nullptr; } static void lcl_GetStartEndCell( const SwCursor& rCursor, SwLayoutFrame *&prStart, SwLayoutFrame *&prEnd ) { OSL_ENSURE( rCursor.GetPointContentNode() && rCursor.GetMarkContentNode(), "Tab selection not at ContentNode" ); Point aPtPos, aMkPos; const SwShellCursor* pShCursor = dynamic_cast(&rCursor); if( pShCursor ) { aPtPos = pShCursor->GetPtPos(); aMkPos = pShCursor->GetMkPos(); } // Robust: SwContentNode* pPointNd = rCursor.GetPointContentNode(); SwContentNode* pMarkNd = rCursor.GetMarkContentNode(); std::pair tmp(aPtPos, true); SwFrame *const pPointFrame = pPointNd ? pPointNd->getLayoutFrame(pPointNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp) : nullptr; tmp.first = aMkPos; SwFrame *const pMarkFrame = pMarkNd ? pMarkNd->getLayoutFrame(pMarkNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp) : nullptr; prStart = pPointFrame ? pPointFrame->GetUpper() : nullptr; prEnd = pMarkFrame ? pMarkFrame->GetUpper() : nullptr; } static bool lcl_GetBoxSel( const SwCursor& rCursor, SwSelBoxes& rBoxes, bool bAllCursor = false ) { const SwTableCursor* pTableCursor = dynamic_cast(&rCursor); if( pTableCursor ) ::GetTableSelCrs( *pTableCursor, rBoxes ); else { const SwPaM *pCurPam = &rCursor, *pSttPam = pCurPam; do { const SwNode* pNd = pCurPam->GetPointNode().FindTableBoxStartNode(); if( pNd ) { SwTableBox* pBox = const_cast(pNd->FindTableNode()->GetTable(). GetTableBox( pNd->GetIndex() )); rBoxes.insert( pBox ); } } while( bAllCursor && pSttPam != ( pCurPam = pCurPam->GetNext()) ); } return !rBoxes.empty(); } static void InsertLine( std::vector& rLineArr, SwTableLine* pLine ) { if( rLineArr.end() == std::find( rLineArr.begin(), rLineArr.end(), pLine ) ) rLineArr.push_back( pLine ); } static bool lcl_IsAnLower( const SwTableLine *pLine, const SwTableLine *pAssumed ) { const SwTableLine *pTmp = pAssumed->GetUpper() ? pAssumed->GetUpper()->GetUpper() : nullptr; while ( pTmp ) { if ( pTmp == pLine ) return true; pTmp = pTmp->GetUpper() ? pTmp->GetUpper()->GetUpper() : nullptr; } return false; } namespace { struct LinesAndTable { std::vector &m_rLines; const SwTable &m_rTable; bool m_bInsertLines; LinesAndTable(std::vector &rL, const SwTable &rTable) : m_rLines(rL), m_rTable(rTable), m_bInsertLines(true) {} }; } static bool FindLine_( FndLine_ & rLine, LinesAndTable* pPara ); static bool FindBox_( FndBox_ & rBox, LinesAndTable* pPara ) { if (!rBox.GetLines().empty()) { pPara->m_bInsertLines = true; for (auto const& rpFndLine : rBox.GetLines()) { FindLine_(*rpFndLine, pPara); } if (pPara->m_bInsertLines) { const SwTableLines &rLines = (rBox.GetBox()) ? rBox.GetBox()->GetTabLines() : pPara->m_rTable.GetTabLines(); if (rBox.GetLines().size() == rLines.size()) { for ( auto pLine : rLines ) ::InsertLine(pPara->m_rLines, pLine); } else pPara->m_bInsertLines = false; } } else if (rBox.GetBox()) { ::InsertLine(pPara->m_rLines, rBox.GetBox()->GetUpper()); } return true; } bool FindLine_( FndLine_& rLine, LinesAndTable* pPara ) { for (auto const& it : rLine.GetBoxes()) { FindBox_(*it, pPara); } return true; } static void lcl_CollectLines( std::vector &rArr, const SwCursor& rCursor, bool bRemoveLines ) { // Collect the selected Boxes first SwSelBoxes aBoxes; if( !::lcl_GetBoxSel( rCursor, aBoxes )) return ; // Copy the selected structure const SwTable &rTable = aBoxes[0]->GetSttNd()->FindTableNode()->GetTable(); LinesAndTable aPara( rArr, rTable ); FndBox_ aFndBox( nullptr, nullptr ); { FndPara aTmpPara( aBoxes, &aFndBox ); ForEach_FndLineCopyCol( const_cast(rTable.GetTabLines()), &aTmpPara ); } // Collect the Lines which only contain selected Boxes ::FindBox_(aFndBox, &aPara); // Remove lines, that have a common superordinate row. // (Not for row split) if ( !bRemoveLines ) return; for ( std::vector::size_type i = 0; i < rArr.size(); ++i ) { SwTableLine *pUpLine = rArr[i]; for ( std::vector::size_type k = 0; k < rArr.size(); ++k ) { if ( k != i && ::lcl_IsAnLower( pUpLine, rArr[k] ) ) { rArr.erase( rArr.begin() + k ); if ( k <= i ) --i; --k; } } } } static void lcl_ProcessRowAttr(std::vector>& rFormatCmp, SwTableLine* pLine, const SfxPoolItem& rNew) { SwFrameFormat *pNewFormat = SwTableFormatCmp::FindNewFormat( rFormatCmp, pLine->GetFrameFormat(), 0 ); if ( nullptr != pNewFormat ) pLine->ChgFrameFormat( static_cast(pNewFormat) ); else { SwFrameFormat *pOld = pLine->GetFrameFormat(); SwFrameFormat *pNew = pLine->ClaimFrameFormat(); pNew->SetFormatAttr( rNew ); rFormatCmp.push_back(std::make_unique(pOld, pNew, 0)); } } static void lcl_ProcessBoxSize(std::vector>& rFormatCmp, SwTableBox* pBox, const SwFormatFrameSize& rNew); static void lcl_ProcessRowSize(std::vector>& rFormatCmp, SwTableLine* pLine, const SwFormatFrameSize& rNew) { lcl_ProcessRowAttr( rFormatCmp, pLine, rNew ); SwTableBoxes &rBoxes = pLine->GetTabBoxes(); for ( auto pBox : rBoxes ) ::lcl_ProcessBoxSize( rFormatCmp, pBox, rNew ); } static void lcl_ProcessBoxSize(std::vector>& rFormatCmp, SwTableBox* pBox, const SwFormatFrameSize& rNew) { SwTableLines &rLines = pBox->GetTabLines(); if ( !rLines.empty() ) { SwFormatFrameSize aSz( rNew ); aSz.SetHeight( rNew.GetHeight() ? rNew.GetHeight() / rLines.size() : 0 ); for ( auto pLine : rLines ) ::lcl_ProcessRowSize( rFormatCmp, pLine, aSz ); } } void SwDoc::SetRowSplit( const SwCursor& rCursor, const SwFormatRowSplit &rNew ) { SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); if( !pTableNd ) return; std::vector aRowArr; // For Lines collecting ::lcl_CollectLines( aRowArr, rCursor, false ); if( aRowArr.empty() ) return; if (GetIDocumentUndoRedo().DoesUndo()) { GetIDocumentUndoRedo().AppendUndo(std::make_unique(*pTableNd)); } std::vector> aFormatCmp; aFormatCmp.reserve( std::max( 255, static_cast(aRowArr.size()) ) ); for( auto pLn : aRowArr ) ::lcl_ProcessRowAttr( aFormatCmp, pLn, rNew ); getIDocumentState().SetModified(); } std::unique_ptr SwDoc::GetRowSplit( const SwCursor& rCursor ) { SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); if( !pTableNd ) return nullptr; std::vector aRowArr; // For Lines collecting ::lcl_CollectLines( aRowArr, rCursor, false ); if( aRowArr.empty() ) return nullptr; SwFormatRowSplit* pSz = &const_cast(aRowArr[0]->GetFrameFormat()->GetRowSplit()); for ( auto pLn : aRowArr ) { if ( pSz->GetValue() != pLn->GetFrameFormat()->GetRowSplit().GetValue() ) { return nullptr; } } return std::make_unique( *pSz ); } /* Class: SwDoc * Methods: SetRowHeight(), GetRowHeight() * * The line height is calculated from the Selection. * Starting with every Cell within the Selection, all Cells are iterated * through in an upwards fashion. * * The topmost Line gets the requested value, all Lines below it get * a respective value that is calculated from the relation of the old and * new size of the topmost Line in the lower line's own size. * * All changed Lines may get an own FrameFormat. * Of course we can only touch every Line once. */ void SwDoc::SetRowHeight( const SwCursor& rCursor, const SwFormatFrameSize &rNew ) { SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); if( !pTableNd ) return; std::vector aRowArr; // For Lines collecting ::lcl_CollectLines( aRowArr, rCursor, true ); if( aRowArr.empty() ) return; if (GetIDocumentUndoRedo().DoesUndo()) { GetIDocumentUndoRedo().AppendUndo(std::make_unique(*pTableNd)); } std::vector> aFormatCmp; aFormatCmp.reserve( std::max( 255, static_cast(aRowArr.size()) ) ); for ( auto pLn : aRowArr ) ::lcl_ProcessRowSize( aFormatCmp, pLn, rNew ); getIDocumentState().SetModified(); } std::unique_ptr SwDoc::GetRowHeight( const SwCursor& rCursor ) { SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); if( !pTableNd ) return nullptr; std::vector aRowArr; // For Lines collecting ::lcl_CollectLines( aRowArr, rCursor, true ); if( aRowArr.empty() ) return nullptr; SwFormatFrameSize* pSz = &const_cast(aRowArr[0]->GetFrameFormat()->GetFrameSize()); for ( auto pLn : aRowArr ) { if ( *pSz != pLn->GetFrameFormat()->GetFrameSize() ) return nullptr; } return std::make_unique( *pSz ); } bool SwDoc::BalanceRowHeight( const SwCursor& rCursor, bool bTstOnly, const bool bOptimize ) { bool bRet = false; SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); if( pTableNd ) { std::vector aRowArr; // For Lines collecting ::lcl_CollectLines( aRowArr, rCursor, true ); if( 1 < aRowArr.size() ) { if( !bTstOnly ) { tools::Long nHeight = 0; sal_Int32 nTotalHeight = 0; for ( auto pLn : aRowArr ) { if (bOptimize) nHeight = 0; SwIterator aIter( *pLn->GetFrameFormat() ); SwFrame* pFrame = aIter.First(); while ( pFrame ) { nHeight = std::max( nHeight, pFrame->getFrameArea().Height() ); pFrame = aIter.Next(); } nTotalHeight += nHeight; } if ( bOptimize ) nHeight = nTotalHeight / aRowArr.size(); SwFormatFrameSize aNew( SwFrameSize::Minimum, 0, nHeight ); if (GetIDocumentUndoRedo().DoesUndo()) { GetIDocumentUndoRedo().AppendUndo( std::make_unique(*pTableNd)); } std::vector> aFormatCmp; aFormatCmp.reserve( std::max( 255, static_cast(aRowArr.size()) ) ); for( auto pLn : aRowArr ) ::lcl_ProcessRowSize( aFormatCmp, pLn, aNew ); getIDocumentState().SetModified(); } bRet = true; } } return bRet; } void SwDoc::SetRowBackground( const SwCursor& rCursor, const SvxBrushItem &rNew ) { SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); if( !pTableNd ) return; std::vector aRowArr; // For Lines collecting ::lcl_CollectLines( aRowArr, rCursor, true ); if( aRowArr.empty() ) return; if (GetIDocumentUndoRedo().DoesUndo()) { GetIDocumentUndoRedo().AppendUndo(std::make_unique(*pTableNd)); } std::vector> aFormatCmp; aFormatCmp.reserve( std::max( 255, static_cast(aRowArr.size()) ) ); for( auto pLn : aRowArr ) ::lcl_ProcessRowAttr( aFormatCmp, pLn, rNew ); getIDocumentState().SetModified(); } bool SwDoc::GetRowBackground( const SwCursor& rCursor, std::unique_ptr& rToFill ) { bool bRet = false; SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); if( pTableNd ) { std::vector aRowArr; // For Lines collecting ::lcl_CollectLines( aRowArr, rCursor, true ); if( !aRowArr.empty() ) { rToFill = aRowArr[0]->GetFrameFormat()->makeBackgroundBrushItem(); bRet = true; for ( std::vector::size_type i = 1; i < aRowArr.size(); ++i ) { std::unique_ptr aAlternative(aRowArr[i]->GetFrameFormat()->makeBackgroundBrushItem()); if ( *rToFill != *aAlternative ) { bRet = false; break; } } } } return bRet; } // has a table row, which is not a tracked deletion bool SwDoc::HasRowNotTracked( const SwCursor& rCursor ) { SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); if( !pTableNd ) return false; std::vector aRowArr; // For Lines collecting ::lcl_CollectLines( aRowArr, rCursor, true ); if( aRowArr.empty() ) return false; SwRedlineTable::size_type nRedlinePos = 0; SwDoc* pDoc = aRowArr[0]->GetFrameFormat()->GetDoc(); const IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess(); for( auto pLn : aRowArr ) { auto pHasTextChangesOnlyProp = pLn->GetFrameFormat()->GetAttrSet().GetItem(RES_PRINT); if ( !pHasTextChangesOnlyProp || pHasTextChangesOnlyProp->GetValue() ) // there is a not tracked row in the table selection return true; // tdf#150666 examine tracked row: it's possible to delete a tracked insertion SwRedlineTable::size_type nPos = pLn->UpdateTextChangesOnly(nRedlinePos); if ( nPos != SwRedlineTable::npos ) { const SwRedlineTable& aRedlineTable = rIDRA.GetRedlineTable(); SwRangeRedline* pTmp = aRedlineTable[ nPos ]; if ( RedlineType::Insert == pTmp->GetType() ) return true; } } return false; } void SwDoc::SetRowNotTracked( const SwCursor& rCursor, const SvxPrintItem &rNew, bool bAll, bool bIns ) { SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); if( !pTableNd ) return; std::vector aRowArr; // For Lines collecting if ( bAll ) { const SwTableLines &rLines = pTableNd->GetTable().GetTabLines(); aRowArr.insert(aRowArr.end(), rLines.begin(), rLines.end()); } else ::lcl_CollectLines( aRowArr, rCursor, true ); if( aRowArr.empty() ) return; if (GetIDocumentUndoRedo().DoesUndo()) { GetIDocumentUndoRedo().AppendUndo(std::make_unique(*pTableNd)); } bool bInsertDummy = !bAll && !bIns && // HasTextChangesOnly == false, i.e. a tracked row change (deletion, if bIns == false) !rNew.GetValue(); std::vector> aFormatCmp; aFormatCmp.reserve( std::max( 255, static_cast(aRowArr.size()) ) ); SwRedlineTable::size_type nRedlinePos = 0; for( auto pLn : aRowArr ) { // tdf#150666 deleting row insertion from the same author needs special handling, // because removing redlines of the author can result an empty line, // which doesn't contain any redline for the tracked row bool bDeletionOfOwnRowInsertion = false; if ( bInsertDummy ) { SwRedlineTable::size_type nPos = pLn->UpdateTextChangesOnly(nRedlinePos); if ( nPos != SwRedlineTable::npos ) { SwDoc* pDoc = pLn->GetFrameFormat()->GetDoc(); IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess(); const SwRedlineTable& aRedlineTable = rIDRA.GetRedlineTable(); SwRangeRedline* pTmp = aRedlineTable[ nPos ]; if ( RedlineType::Insert == pTmp->GetType() && rIDRA.GetRedlineAuthor() == pTmp->GetRedlineData().GetAuthor() && pTmp->GetText()[0] == CH_TXT_TRACKED_DUMMY_CHAR ) { bDeletionOfOwnRowInsertion = true; } } } ::lcl_ProcessRowAttr( aFormatCmp, pLn, rNew ); // as a workaround for the rows without text content, // add a redline with invisible text CH_TXT_TRACKED_DUMMY_CHAR // (unless the table is part of a bigger deletion, where the // new redline can cause a problem) if ( bInsertDummy && (pLn->IsEmpty() || bDeletionOfOwnRowInsertion ) ) { ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); SwNodeIndex aInsPos( *(pLn->GetTabBoxes()[0]->GetSttNd()), 1 ); RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags(); getIDocumentRedlineAccess().SetRedlineFlags_intern(RedlineFlags::NONE); SwPaM aPaM(aInsPos); getIDocumentContentOperations().InsertString( aPaM, OUStringChar(CH_TXT_TRACKED_DUMMY_CHAR) ); aPaM.SetMark(); getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); getIDocumentContentOperations().DeleteAndJoin( aPaM ); } } getIDocumentState().SetModified(); } static void InsertCell( std::vector& rCellArr, SwCellFrame* pCellFrame ) { if( rCellArr.end() == std::find( rCellArr.begin(), rCellArr.end(), pCellFrame ) ) rCellArr.push_back( pCellFrame ); } static void lcl_CollectCells( std::vector &rArr, const SwRect &rUnion, SwTabFrame *pTab ) { SwLayoutFrame *pCell = pTab->FirstCell(); do { // If the Cell contains a CellFrame, we need to use it // in order to get to the Cell while ( !pCell->IsCellFrame() ) pCell = pCell->GetUpper(); OSL_ENSURE( pCell, "Frame is not a Cell" ); if ( rUnion.Overlaps( pCell->getFrameArea() ) ) ::InsertCell( rArr, static_cast(pCell) ); // Make sure the Cell is left (Areas) SwLayoutFrame *pTmp = pCell; do { pTmp = pTmp->GetNextLayoutLeaf(); } while ( pCell->IsAnLower( pTmp ) ); pCell = pTmp; } while( pCell && pTab->IsAnLower( pCell ) ); } void SwDoc::SetTabBorders( const SwCursor& rCursor, const SfxItemSet& rSet ) { SwContentNode* pCntNd = rCursor.GetPoint()->GetNode().GetContentNode(); SwTableNode* pTableNd = pCntNd ? pCntNd->FindTableNode() : nullptr; if( !pTableNd ) return ; SwLayoutFrame *pStart, *pEnd; ::lcl_GetStartEndCell( rCursor, pStart, pEnd ); SwSelUnions aUnions; ::MakeSelUnions( aUnions, pStart, pEnd ); if( aUnions.empty() ) return; SwTable& rTable = pTableNd->GetTable(); if (GetIDocumentUndoRedo().DoesUndo()) { GetIDocumentUndoRedo().AppendUndo( std::make_unique(*pTableNd) ); } std::vector> aFormatCmp; aFormatCmp.reserve( 255 ); const SvxBoxItem* pSetBox; const SvxBoxInfoItem *pSetBoxInfo; const SvxBorderLine* pLeft = nullptr; const SvxBorderLine* pRight = nullptr; const SvxBorderLine* pTop = nullptr; const SvxBorderLine* pBottom = nullptr; const SvxBorderLine* pHori = nullptr; const SvxBorderLine* pVert = nullptr; bool bHoriValid = true, bVertValid = true, bTopValid = true, bBottomValid = true, bLeftValid = true, bRightValid = true; // The Flags in the BoxInfo Item decide whether a BorderLine is valid! pSetBoxInfo = rSet.GetItemIfSet( SID_ATTR_BORDER_INNER, false ); if( pSetBoxInfo ) { pHori = pSetBoxInfo->GetHori(); pVert = pSetBoxInfo->GetVert(); bHoriValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::HORI); bVertValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::VERT); // Do we want to evaluate these? bTopValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::TOP); bBottomValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::BOTTOM); bLeftValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::LEFT); bRightValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::RIGHT); } pSetBox = rSet.GetItemIfSet( RES_BOX, false ); if( pSetBox ) { pLeft = pSetBox->GetLeft(); pRight = pSetBox->GetRight(); pTop = pSetBox->GetTop(); pBottom = pSetBox->GetBottom(); } else { // Not set, thus not valid values bTopValid = bBottomValid = bLeftValid = bRightValid = false; pSetBox = nullptr; } bool bFirst = true; for ( SwSelUnions::size_type i = 0; i < aUnions.size(); ++i ) { SwSelUnion *pUnion = &aUnions[i]; SwTabFrame *pTab = pUnion->GetTable(); const SwRect &rUnion = pUnion->GetUnion(); const bool bLast = (i == aUnions.size() - 1); std::vector aCellArr; aCellArr.reserve( 255 ); ::lcl_CollectCells( aCellArr, pUnion->GetUnion(), pTab ); // All Cell Borders that match the UnionRect or extend it are // Outer Borders. All others are Inner Borders. // New: The Outer Borders can, depending on whether it's a // Start/Middle/Follow Table (for Selection via FollowTabs), // also not be Outer Borders. // Outer Borders are set on the left, right, at the top and at the bottom. // Inner Borders are only set at the top and on the left. for ( auto pCell : aCellArr ) { const bool bVert = pTab->IsVertical(); const bool bRTL = pTab->IsRightToLeft(); bool bTopOver, bLeftOver, bRightOver, bBottomOver; if ( bVert ) { bTopOver = pCell->getFrameArea().Right() >= rUnion.Right(); bLeftOver = pCell->getFrameArea().Top() <= rUnion.Top(); bRightOver = pCell->getFrameArea().Bottom() >= rUnion.Bottom(); bBottomOver = pCell->getFrameArea().Left() <= rUnion.Left(); } else { bTopOver = pCell->getFrameArea().Top() <= rUnion.Top(); bLeftOver = pCell->getFrameArea().Left() <= rUnion.Left(); bRightOver = pCell->getFrameArea().Right() >= rUnion.Right(); bBottomOver = pCell->getFrameArea().Bottom() >= rUnion.Bottom(); } if ( bRTL ) std::swap( bLeftOver, bRightOver ); // Do not set anything by default in HeadlineRepeats if ( pTab->IsFollow() && ( pTab->IsInHeadline( *pCell ) || // Same holds for follow flow rows pCell->IsInFollowFlowRow() ) ) continue; SvxBoxItem aBox( pCell->GetFormat()->GetBox() ); sal_Int16 nType = 0; // Top Border if( bTopValid ) { if ( bFirst && bTopOver ) { aBox.SetLine( pTop, SvxBoxItemLine::TOP ); nType |= 0x0001; } else if ( bHoriValid ) { aBox.SetLine( nullptr, SvxBoxItemLine::TOP ); nType |= 0x0002; } } // Fix fdo#62470 correct the input for RTL table if (bRTL) { if( bLeftOver && bRightOver) { if ( bLeftValid ) { aBox.SetLine( pLeft, SvxBoxItemLine::RIGHT ); nType |= 0x0010; } if ( bRightValid ) { aBox.SetLine( pRight, SvxBoxItemLine::LEFT ); nType |= 0x0004; } } else { if ( bLeftValid ) { aBox.SetLine( bRightOver ? pLeft : nullptr, SvxBoxItemLine::RIGHT ); if (bVertValid) nType |= 0x0020; else nType |= 0x0010; } if ( bLeftOver ) { if ( bRightValid ) { aBox.SetLine( pRight, SvxBoxItemLine::LEFT ); nType |= 0x0004; } } else if ( bVertValid ) { aBox.SetLine( pVert, SvxBoxItemLine::LEFT ); nType |= 0x0008; } } } else { // Left Border if ( bLeftOver ) { if( bLeftValid ) { aBox.SetLine( pLeft, SvxBoxItemLine::LEFT ); nType |= 0x0004; } } else if( bVertValid ) { aBox.SetLine( pVert, SvxBoxItemLine::LEFT ); nType |= 0x0008; } // Right Border if( bRightValid ) { if ( bRightOver ) { aBox.SetLine( pRight, SvxBoxItemLine::RIGHT ); nType |= 0x0010; } else if ( bVertValid ) { aBox.SetLine( nullptr, SvxBoxItemLine::RIGHT ); nType |= 0x0020; } } } // Bottom Border if ( bLast && bBottomOver ) { if( bBottomValid ) { aBox.SetLine( pBottom, SvxBoxItemLine::BOTTOM ); nType |= 0x0040; } } else if( bHoriValid ) { aBox.SetLine( pHori, SvxBoxItemLine::BOTTOM ); nType |= 0x0080; } if( pSetBox ) { for( SvxBoxItemLine k : o3tl::enumrange() ) aBox.SetDistance( pSetBox->GetDistance( k ), k ); } SwTableBox *pBox = const_cast(pCell->GetTabBox()); SwFrameFormat *pNewFormat = SwTableFormatCmp::FindNewFormat( aFormatCmp, pBox->GetFrameFormat(), nType ); if ( nullptr != pNewFormat ) pBox->ChgFrameFormat( static_cast(pNewFormat) ); else { SwFrameFormat *pOld = pBox->GetFrameFormat(); SwFrameFormat *pNew = pBox->ClaimFrameFormat(); pNew->SetFormatAttr( aBox ); aFormatCmp.push_back(std::make_unique(pOld, pNew, nType)); } } bFirst = false; } SwHTMLTableLayout *pTableLayout = rTable.GetHTMLTableLayout(); if( pTableLayout ) { SwContentFrame* pFrame = rCursor.GetPointContentNode()->getLayoutFrame( rCursor.GetPointContentNode()->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() ); SwTabFrame* pTabFrame = pFrame->ImplFindTabFrame(); pTableLayout->BordersChanged( pTableLayout->GetBrowseWidthByTabFrame( *pTabFrame ) ); } ::ClearFEShellTabCols(*this, nullptr); getIDocumentState().SetModified(); } static void lcl_SetLineStyle( SvxBorderLine *pToSet, const Color *pColor, const SvxBorderLine *pBorderLine) { if ( pBorderLine ) { if ( !pColor ) { Color aTmp( pToSet->GetColor() ); *pToSet = *pBorderLine; pToSet->SetColor( aTmp ); } else *pToSet = *pBorderLine; } if ( pColor ) pToSet->SetColor( *pColor ); } void SwDoc::SetTabLineStyle( const SwCursor& rCursor, const Color* pColor, bool bSetLine, const SvxBorderLine* pBorderLine ) { SwContentNode* pCntNd = rCursor.GetPoint()->GetNode().GetContentNode(); SwTableNode* pTableNd = pCntNd ? pCntNd->FindTableNode() : nullptr; if( !pTableNd ) return ; SwLayoutFrame *pStart, *pEnd; ::lcl_GetStartEndCell( rCursor, pStart, pEnd ); SwSelUnions aUnions; ::MakeSelUnions( aUnions, pStart, pEnd ); if( aUnions.empty() ) return; SwTable& rTable = pTableNd->GetTable(); if (GetIDocumentUndoRedo().DoesUndo()) { GetIDocumentUndoRedo().AppendUndo(std::make_unique(*pTableNd)); } SvxBorderLine aDefaultBorder(pBorderLine ? *pBorderLine : SvxBorderLine(pColor, SvxBorderLineWidth::VeryThin)); if (pColor && pBorderLine) aDefaultBorder.SetColor(*pColor); for( auto &rU : aUnions ) { SwSelUnion *pUnion = &rU; SwTabFrame *pTab = pUnion->GetTable(); std::vector aCellArr; aCellArr.reserve( 255 ); ::lcl_CollectCells( aCellArr, pUnion->GetUnion(), pTab ); for ( auto pCell : aCellArr ) { // Do not set anything by default in HeadlineRepeats if ( pTab->IsFollow() && pTab->IsInHeadline( *pCell ) ) continue; const_cast(pCell->GetTabBox())->ClaimFrameFormat(); SwFrameFormat *pFormat = pCell->GetFormat(); std::unique_ptr aBox(pFormat->GetBox().Clone()); SvxBorderLine* pTop = aBox->GetTop(); SvxBorderLine* pBot = aBox->GetBottom(); SvxBorderLine* pLeft = aBox->GetLeft(); SvxBorderLine* pRight = aBox->GetRight(); if ( !pBorderLine && bSetLine ) { aBox.reset(::GetDfltAttr(RES_BOX)->Clone()); } else if ((pColor || pBorderLine) && !pTop && !pBot && !pLeft && !pRight) { aBox->SetLine(&aDefaultBorder, SvxBoxItemLine::TOP); aBox->SetLine(&aDefaultBorder, SvxBoxItemLine::BOTTOM); aBox->SetLine(&aDefaultBorder, SvxBoxItemLine::LEFT); aBox->SetLine(&aDefaultBorder, SvxBoxItemLine::RIGHT); } else { if (pTop) ::lcl_SetLineStyle(pTop, pColor, pBorderLine); if (pBot) ::lcl_SetLineStyle(pBot, pColor, pBorderLine); if (pLeft) ::lcl_SetLineStyle(pLeft, pColor, pBorderLine); if (pRight) ::lcl_SetLineStyle(pRight, pColor, pBorderLine); } pFormat->SetFormatAttr( *aBox ); } } SwHTMLTableLayout *pTableLayout = rTable.GetHTMLTableLayout(); if( pTableLayout ) { SwContentFrame* pFrame = rCursor.GetPointContentNode()->getLayoutFrame( rCursor.GetPointContentNode()->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() ); SwTabFrame* pTabFrame = pFrame->ImplFindTabFrame(); pTableLayout->BordersChanged( pTableLayout->GetBrowseWidthByTabFrame( *pTabFrame ) ); } ::ClearFEShellTabCols(*this, nullptr); getIDocumentState().SetModified(); } void SwDoc::GetTabBorders( const SwCursor& rCursor, SfxItemSet& rSet ) { SwContentNode* pCntNd = rCursor.GetPoint()->GetNode().GetContentNode(); SwTableNode* pTableNd = pCntNd ? pCntNd->FindTableNode() : nullptr; if( !pTableNd ) return ; SwLayoutFrame *pStart, *pEnd; ::lcl_GetStartEndCell( rCursor, pStart, pEnd ); SwSelUnions aUnions; ::MakeSelUnions( aUnions, pStart, pEnd ); if( aUnions.empty() ) return; SvxBoxItem aSetBox ( rSet.Get(RES_BOX ) ); SvxBoxInfoItem aSetBoxInfo( rSet.Get(SID_ATTR_BORDER_INNER) ); bool bTopSet = false, bBottomSet = false, bLeftSet = false, bRightSet = false, bHoriSet = false, bVertSet = false, bDistanceSet = false, bRTLTab = false; aSetBoxInfo.ResetFlags(); for ( SwSelUnions::size_type i = 0; i < aUnions.size(); ++i ) { SwSelUnion *pUnion = &aUnions[i]; const SwTabFrame *pTab = pUnion->GetTable(); const SwRect &rUnion = pUnion->GetUnion(); const bool bFirst = i == 0; const bool bLast = (i == aUnions.size() - 1); std::vector aCellArr; aCellArr.reserve(255); ::lcl_CollectCells( aCellArr, rUnion, const_cast(pTab) ); for ( auto pCell : aCellArr ) { const bool bVert = pTab->IsVertical(); const bool bRTL = bRTLTab = pTab->IsRightToLeft(); bool bTopOver, bLeftOver, bRightOver, bBottomOver; if ( bVert ) { bTopOver = pCell->getFrameArea().Right() >= rUnion.Right(); bLeftOver = pCell->getFrameArea().Top() <= rUnion.Top(); bRightOver = pCell->getFrameArea().Bottom() >= rUnion.Bottom(); bBottomOver = pCell->getFrameArea().Left() <= rUnion.Left(); } else { bTopOver = pCell->getFrameArea().Top() <= rUnion.Top(); bLeftOver = pCell->getFrameArea().Left() <= rUnion.Left(); bRightOver = pCell->getFrameArea().Right() >= rUnion.Right(); bBottomOver = pCell->getFrameArea().Bottom() >= rUnion.Bottom(); } if ( bRTL ) std::swap( bLeftOver, bRightOver ); const SwFrameFormat *pFormat = pCell->GetFormat(); const SvxBoxItem &rBox = pFormat->GetBox(); // Top Border if ( bFirst && bTopOver ) { if (aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::TOP)) { if ( !bTopSet ) { bTopSet = true; aSetBox.SetLine( rBox.GetTop(), SvxBoxItemLine::TOP ); } else if ((aSetBox.GetTop() && rBox.GetTop() && (*aSetBox.GetTop() != *rBox.GetTop())) || ((!aSetBox.GetTop()) != (!rBox.GetTop()))) // != expression is true, if one and only one of the two pointers is !0 { aSetBoxInfo.SetValid(SvxBoxInfoItemValidFlags::TOP, false ); aSetBox.SetLine( nullptr, SvxBoxItemLine::TOP ); } } } // Left Border if ( bLeftOver ) { if (aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::LEFT)) { if ( !bLeftSet ) { bLeftSet = true; aSetBox.SetLine( rBox.GetLeft(), SvxBoxItemLine::LEFT ); } else if ((aSetBox.GetLeft() && rBox.GetLeft() && (*aSetBox.GetLeft() != *rBox.GetLeft())) || ((!aSetBox.GetLeft()) != (!rBox.GetLeft()))) { aSetBoxInfo.SetValid(SvxBoxInfoItemValidFlags::LEFT, false ); aSetBox.SetLine( nullptr, SvxBoxItemLine::LEFT ); } } } else { if (aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::VERT)) { if ( !bVertSet ) { bVertSet = true; aSetBoxInfo.SetLine( rBox.GetLeft(), SvxBoxInfoItemLine::VERT ); } else if ((aSetBoxInfo.GetVert() && rBox.GetLeft() && (*aSetBoxInfo.GetVert() != *rBox.GetLeft())) || ((!aSetBoxInfo.GetVert()) != (!rBox.GetLeft()))) { aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::VERT, false ); aSetBoxInfo.SetLine( nullptr, SvxBoxInfoItemLine::VERT ); } } } // Right Border if ( aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::RIGHT) && bRightOver ) { if ( !bRightSet ) { bRightSet = true; aSetBox.SetLine( rBox.GetRight(), SvxBoxItemLine::RIGHT ); } else if ((aSetBox.GetRight() && rBox.GetRight() && (*aSetBox.GetRight() != *rBox.GetRight())) || (!aSetBox.GetRight() != !rBox.GetRight())) { aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::RIGHT, false ); aSetBox.SetLine( nullptr, SvxBoxItemLine::RIGHT ); } } // Bottom Border if ( bLast && bBottomOver ) { if ( aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::BOTTOM) ) { if ( !bBottomSet ) { bBottomSet = true; aSetBox.SetLine( rBox.GetBottom(), SvxBoxItemLine::BOTTOM ); } else if ((aSetBox.GetBottom() && rBox.GetBottom() && (*aSetBox.GetBottom() != *rBox.GetBottom())) || (!aSetBox.GetBottom() != !rBox.GetBottom())) { aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::BOTTOM, false ); aSetBox.SetLine( nullptr, SvxBoxItemLine::BOTTOM ); } } } // In all Lines, except for the last one, the horizontal Line // is taken from the Bottom Line. else { if (aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::HORI)) { if ( !bHoriSet ) { bHoriSet = true; aSetBoxInfo.SetLine( rBox.GetBottom(), SvxBoxInfoItemLine::HORI ); } else if ((aSetBoxInfo.GetHori() && rBox.GetBottom() && (*aSetBoxInfo.GetHori() != *rBox.GetBottom())) || ((!aSetBoxInfo.GetHori()) != (!rBox.GetBottom()))) { aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::HORI, false ); aSetBoxInfo.SetLine( nullptr, SvxBoxInfoItemLine::HORI ); } } } // Distance to text if (aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::DISTANCE)) { if( !bDistanceSet ) // Set on first iteration { bDistanceSet = true; for( SvxBoxItemLine k : o3tl::enumrange() ) aSetBox.SetDistance( rBox.GetDistance( k ), k ); } else { for( SvxBoxItemLine k : o3tl::enumrange() ) if( aSetBox.GetDistance( k ) != rBox.GetDistance( k ) ) { aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::DISTANCE, false ); aSetBox.SetAllDistances(0); break; } } } } } // fdo#62470 fix the reading for table format. if ( bRTLTab ) { SvxBoxItem aTempBox ( rSet.Get(RES_BOX ) ); SvxBoxInfoItem aTempBoxInfo( rSet.Get(SID_ATTR_BORDER_INNER) ); aTempBox.SetLine( aSetBox.GetRight(), SvxBoxItemLine::RIGHT); aSetBox.SetLine( aSetBox.GetLeft(), SvxBoxItemLine::RIGHT); aSetBox.SetLine( aTempBox.GetRight(), SvxBoxItemLine::LEFT); aTempBoxInfo.SetValid( SvxBoxInfoItemValidFlags::LEFT, aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::LEFT) ); aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::LEFT, aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::RIGHT) ); aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::RIGHT, aTempBoxInfo.IsValid(SvxBoxInfoItemValidFlags::LEFT) ); } rSet.Put( aSetBox ); rSet.Put( aSetBoxInfo ); } void SwDoc::SetBoxAttr( const SwCursor& rCursor, const SfxPoolItem &rNew ) { SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); SwSelBoxes aBoxes; if( !(pTableNd && ::lcl_GetBoxSel( rCursor, aBoxes, true )) ) return; SwTable& rTable = pTableNd->GetTable(); if (GetIDocumentUndoRedo().DoesUndo()) { GetIDocumentUndoRedo().AppendUndo( std::make_unique(*pTableNd) ); } std::vector> aFormatCmp; aFormatCmp.reserve(std::max(255, aBoxes.size())); for (size_t i = 0; i < aBoxes.size(); ++i) { SwTableBox *pBox = aBoxes[i]; SwFrameFormat *pNewFormat = SwTableFormatCmp::FindNewFormat( aFormatCmp, pBox->GetFrameFormat(), 0 ); if ( nullptr != pNewFormat ) pBox->ChgFrameFormat( static_cast(pNewFormat) ); else { SwFrameFormat *pOld = pBox->GetFrameFormat(); SwFrameFormat *pNew = pBox->ClaimFrameFormat(); pNew->SetFormatAttr( rNew ); aFormatCmp.push_back(std::make_unique(pOld, pNew, 0)); } pBox->SetDirectFormatting(true); } SwHTMLTableLayout *pTableLayout = rTable.GetHTMLTableLayout(); if( pTableLayout ) { SwContentFrame* pFrame = rCursor.GetPointContentNode()->getLayoutFrame( rCursor.GetPointContentNode()->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() ); SwTabFrame* pTabFrame = pFrame->ImplFindTabFrame(); pTableLayout->Resize( pTableLayout->GetBrowseWidthByTabFrame( *pTabFrame ), true ); } getIDocumentState().SetModified(); } bool SwDoc::GetBoxAttr( const SwCursor& rCursor, std::unique_ptr& rToFill ) { // tdf#144843 calling GetBoxAttr *requires* object assert(rToFill && "requires object here"); bool bRet = false; SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); SwSelBoxes aBoxes; if( pTableNd && lcl_GetBoxSel( rCursor, aBoxes )) { bRet = true; bool bOneFound = false; const sal_uInt16 nWhich = rToFill->Which(); for (size_t i = 0; i < aBoxes.size(); ++i) { switch ( nWhich ) { case RES_BACKGROUND: { std::unique_ptr xBack = aBoxes[i]->GetFrameFormat()->makeBackgroundBrushItem(); if( !bOneFound ) { rToFill = std::move(xBack); bOneFound = true; } else if( *rToFill != *xBack ) bRet = false; } break; case RES_FRAMEDIR: { const SvxFrameDirectionItem& rDir = aBoxes[i]->GetFrameFormat()->GetFrameDir(); if( !bOneFound ) { rToFill.reset(rDir.Clone()); bOneFound = true; } else if( rToFill && *rToFill != rDir ) bRet = false; } break; case RES_VERT_ORIENT: { const SwFormatVertOrient& rOrient = aBoxes[i]->GetFrameFormat()->GetVertOrient(); if( !bOneFound ) { rToFill.reset(rOrient.Clone()); bOneFound = true; } else if( rToFill && *rToFill != rOrient ) bRet = false; } break; } if ( !bRet ) break; } } return bRet; } void SwDoc::SetBoxAlign( const SwCursor& rCursor, sal_uInt16 nAlign ) { OSL_ENSURE( nAlign == text::VertOrientation::NONE || nAlign == text::VertOrientation::CENTER || nAlign == text::VertOrientation::BOTTOM, "Wrong alignment" ); SwFormatVertOrient aVertOri( 0, nAlign ); SetBoxAttr( rCursor, aVertOri ); } sal_uInt16 SwDoc::GetBoxAlign( const SwCursor& rCursor ) { sal_uInt16 nAlign = USHRT_MAX; SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); SwSelBoxes aBoxes; if( pTableNd && ::lcl_GetBoxSel( rCursor, aBoxes )) { for (size_t i = 0; i < aBoxes.size(); ++i) { const SwFormatVertOrient &rOri = aBoxes[i]->GetFrameFormat()->GetVertOrient(); if( USHRT_MAX == nAlign ) nAlign = o3tl::narrowing(rOri.GetVertOrient()); else if( rOri.GetVertOrient() != nAlign ) { nAlign = USHRT_MAX; break; } } } return nAlign; } static sal_uInt16 lcl_CalcCellFit( const SwLayoutFrame *pCell ) { SwTwips nRet = 0; const SwFrame *pFrame = pCell->Lower(); // The whole Line SwRectFnSet aRectFnSet(pCell); while ( pFrame ) { const SwTwips nAdd = aRectFnSet.GetWidth(pFrame->getFrameArea()) - aRectFnSet.GetWidth(pFrame->getFramePrintArea()); // pFrame does not necessarily have to be a SwTextFrame! const SwTwips nCalcFitToContent = pFrame->IsTextFrame() ? const_cast(static_cast(pFrame))->CalcFitToContent() : aRectFnSet.GetWidth(pFrame->getFramePrintArea()); nRet = std::max( nRet, nCalcFitToContent + nAdd ); pFrame = pFrame->GetNext(); } // Surrounding border as well as left and Right Border also need to be respected nRet += aRectFnSet.GetWidth(pCell->getFrameArea()) - aRectFnSet.GetWidth(pCell->getFramePrintArea()); // To compensate for the accuracy of calculation later on in SwTable::SetTabCols // we keep adding up a little. nRet += COLFUZZY; return o3tl::narrowing(std::max( SwTwips(MINLAY), nRet )); } /* The Line is within the Selection but not outlined by the TabCols. * * That means that the Line has been "split" by other Cells due to the * two-dimensional representation used. Thus, we have to distribute the cell's * default or minimum value amongst the Cell it has been split by. * * First, we collect the Columns (not the Column separators) which overlap * with the Cell. We then distribute the desired value according to the * amount of overlapping amongst the Cells. * * A Cell's default value stays the same if it already has a larger value than * the desired one. It's overwritten if it's smaller. */ static void lcl_CalcSubColValues( std::vector &rToFill, const SwTabCols &rCols, const SwLayoutFrame *pCell, const SwLayoutFrame *pTab, bool bWishValues ) { const sal_uInt16 nWish = bWishValues ? ::lcl_CalcCellFit( pCell ) : MINLAY + sal_uInt16(pCell->getFrameArea().Width() - pCell->getFramePrintArea().Width()); SwRectFnSet aRectFnSet(pTab); for ( size_t i = 0 ; i <= rCols.Count(); ++i ) { tools::Long nColLeft = i == 0 ? rCols.GetLeft() : rCols[i-1]; tools::Long nColRight = i == rCols.Count() ? rCols.GetRight() : rCols[i]; nColLeft += rCols.GetLeftMin(); nColRight += rCols.GetLeftMin(); // Adapt values to the proportions of the Table (Follows) if ( rCols.GetLeftMin() != aRectFnSet.GetLeft(pTab->getFrameArea()) ) { const tools::Long nDiff = aRectFnSet.GetLeft(pTab->getFrameArea()) - rCols.GetLeftMin(); nColLeft += nDiff; nColRight += nDiff; } const tools::Long nCellLeft = aRectFnSet.GetLeft(pCell->getFrameArea()); const tools::Long nCellRight = aRectFnSet.GetRight(pCell->getFrameArea()); // Calculate overlapping value tools::Long nWidth = 0; if ( nColLeft <= nCellLeft && nColRight >= (nCellLeft+COLFUZZY) ) nWidth = nColRight - nCellLeft; else if ( nColLeft <= (nCellRight-COLFUZZY) && nColRight >= nCellRight ) nWidth = nCellRight - nColLeft; else if ( nColLeft >= nCellLeft && nColRight <= nCellRight ) nWidth = nColRight - nColLeft; if ( nWidth && pCell->getFrameArea().Width() ) { tools::Long nTmp = nWidth * nWish / pCell->getFrameArea().Width(); if ( o3tl::make_unsigned(nTmp) > rToFill[i] ) rToFill[i] = sal_uInt16(nTmp); } } } /** * Retrieves new values to set the TabCols. * * We do not iterate over the TabCols' entries, but over the gaps that describe Cells. * We set TabCol entries for which we did not calculate Cells to 0. * * @param bWishValues == true: We calculate the desired value of all affected * Cells for the current Selection/current Cell. * If more Cells are within a Column, the highest * desired value is returned. * We set TabCol entries for which we did not calculate * Cells to 0. * * @param bWishValues == false: The Selection is expanded vertically. * We calculate the minimum value for every * Column in the TabCols that intersects with the * Selection. */ static void lcl_CalcColValues( std::vector &rToFill, const SwTabCols &rCols, const SwLayoutFrame *pStart, const SwLayoutFrame *pEnd, bool bWishValues ) { SwSelUnions aUnions; ::MakeSelUnions( aUnions, pStart, pEnd, bWishValues ? SwTableSearchType::NONE : SwTableSearchType::Col ); for ( auto &rU : aUnions ) { SwSelUnion *pSelUnion = &rU; const SwTabFrame *pTab = pSelUnion->GetTable(); const SwRect &rUnion = pSelUnion->GetUnion(); SwRectFnSet aRectFnSet(pTab); bool bRTL = pTab->IsRightToLeft(); const SwLayoutFrame *pCell = pTab->FirstCell(); if (!pCell) continue; do { if ( pCell->IsCellFrame() && pCell->FindTabFrame() == pTab && ::IsFrameInTableSel( rUnion, pCell ) ) { const tools::Long nCLeft = aRectFnSet.GetLeft(pCell->getFrameArea()); const tools::Long nCRight = aRectFnSet.GetRight(pCell->getFrameArea()); bool bNotInCols = true; for ( size_t i = 0; i <= rCols.Count(); ++i ) { sal_uInt16 nFit = rToFill[i]; tools::Long nColLeft = i == 0 ? rCols.GetLeft() : rCols[i-1]; tools::Long nColRight = i == rCols.Count() ? rCols.GetRight() : rCols[i]; if ( bRTL ) { tools::Long nTmpRight = nColRight; nColRight = rCols.GetRight() - nColLeft; nColLeft = rCols.GetRight() - nTmpRight; } nColLeft += rCols.GetLeftMin(); nColRight += rCols.GetLeftMin(); // Adapt values to the proportions of the Table (Follows) tools::Long nLeftA = nColLeft; tools::Long nRightA = nColRight; if ( rCols.GetLeftMin() != sal_uInt16(aRectFnSet.GetLeft(pTab->getFrameArea())) ) { const tools::Long nDiff = aRectFnSet.GetLeft(pTab->getFrameArea()) - rCols.GetLeftMin(); nLeftA += nDiff; nRightA += nDiff; } // We don't want to take a too close look if ( ::IsSame(nCLeft, nLeftA) && ::IsSame(nCRight, nRightA)) { bNotInCols = false; if ( bWishValues ) { const sal_uInt16 nWish = ::lcl_CalcCellFit( pCell ); if ( nWish > nFit ) nFit = nWish; } else { const sal_uInt16 nMin = MINLAY + sal_uInt16(pCell->getFrameArea().Width() - pCell->getFramePrintArea().Width()); if ( !nFit || nMin < nFit ) nFit = nMin; } if ( rToFill[i] < nFit ) rToFill[i] = nFit; } } if ( bNotInCols ) ::lcl_CalcSubColValues( rToFill, rCols, pCell, pTab, bWishValues ); } do { pCell = pCell->GetNextLayoutLeaf(); } while( pCell && pCell->getFrameArea().Width() == 0 ); } while ( pCell && pTab->IsAnLower( pCell ) ); } } void SwDoc::AdjustCellWidth( const SwCursor& rCursor, const bool bBalance, const bool bNoShrink ) { // Check whether the current Cursor has it's Point/Mark in a Table SwContentNode* pCntNd = rCursor.GetPoint()->GetNode().GetContentNode(); SwTableNode* pTableNd = pCntNd ? pCntNd->FindTableNode() : nullptr; if( !pTableNd ) return ; SwLayoutFrame *pStart, *pEnd; ::lcl_GetStartEndCell( rCursor, pStart, pEnd ); // Collect TabCols; we reset the Table with them SwFrame* pBoxFrame = pStart; while( pBoxFrame && !pBoxFrame->IsCellFrame() ) pBoxFrame = pBoxFrame->GetUpper(); if ( !pBoxFrame ) return; // Robust SwTabCols aTabCols; GetTabCols( aTabCols, static_cast(pBoxFrame) ); if ( ! aTabCols.Count() ) return; std::vector aWish(aTabCols.Count() + 1); std::vector aMins(aTabCols.Count() + 1); ::lcl_CalcColValues( aWish, aTabCols, pStart, pEnd, /*bWishValues=*/true ); // It's more robust if we calculate the minimum values for the whole Table const SwTabFrame *pTab = pStart->ImplFindTabFrame(); pStart = const_cast(static_cast(pTab->FirstCell())); pEnd = const_cast(pTab->FindLastContentOrTable()->GetUpper()); while( !pEnd->IsCellFrame() ) pEnd = pEnd->GetUpper(); ::lcl_CalcColValues( aMins, aTabCols, pStart, pEnd, /*bWishValues=*/false ); sal_uInt16 nSelectedWidth = 0, nCols = 0; float fTotalWish = 0; if ( bBalance || bNoShrink ) { // Find the combined size of the selected columns for ( size_t i = 0; i <= aTabCols.Count(); ++i ) { if ( aWish[i] ) { if ( i == 0 ) nSelectedWidth += aTabCols[i] - aTabCols.GetLeft(); else if ( i == aTabCols.Count() ) nSelectedWidth += aTabCols.GetRight() - aTabCols[i-1]; else nSelectedWidth += aTabCols[i] - aTabCols[i-1]; ++nCols; } fTotalWish += aWish[i]; } // bBalance: Distribute the width evenly if (bBalance) { assert(nCols); const sal_uInt16 nEqualWidth = nCols ? nSelectedWidth / nCols : 0; for (sal_uInt16 & rn : aWish) if (rn) rn = nEqualWidth; } } const tools::Long nOldRight = aTabCols.GetRight(); // In order to make the implementation easier, but still use the available // space properly, we do this twice. // The problem: The first column is getting wider, the others get slimmer // only afterwards. // The first column's desired width would be discarded as it would cause // the Table's width to exceed the maximum width. const tools::Long nMaxRight = std::max(aTabCols.GetRightMax(), nOldRight); const sal_uInt16 nEqualWidth = (nMaxRight - aTabCols.GetLeft()) / (aTabCols.Count() + 1); const sal_Int16 nTablePadding = nSelectedWidth - fTotalWish; for ( int k = 0; k < 2; ++k ) { for ( size_t i = 0; i <= aTabCols.Count(); ++i ) { // bNoShrink: distribute excess space proportionately on pass 2. if ( bNoShrink && k && nTablePadding > 0 && fTotalWish > 0 ) aWish[i] += round( aWish[i] / fTotalWish * nTablePadding ); // First pass is primarily a shrink pass. Give all columns a chance // to grow by requesting the maximum width as "balanced". // Second pass is a first-come, first-served chance to max out. int nDiff = k ? aWish[i] : std::min(aWish[i], nEqualWidth); if ( nDiff ) { int nMin = aMins[i]; if ( nMin > nDiff ) nDiff = nMin; if ( i == 0 ) { if( aTabCols.Count() ) nDiff -= aTabCols[0] - aTabCols.GetLeft(); else nDiff -= aTabCols.GetRight() - aTabCols.GetLeft(); } else if ( i == aTabCols.Count() ) nDiff -= aTabCols.GetRight() - aTabCols[i-1]; else nDiff -= aTabCols[i] - aTabCols[i-1]; tools::Long nTabRight = aTabCols.GetRight() + nDiff; // If the Table would become (or is already) too wide, // restrict the column growth to the allowed maximum. if (!bBalance && nTabRight > nMaxRight) { const tools::Long nTmpD = nTabRight - nMaxRight; nDiff -= nTmpD; nTabRight -= nTmpD; } // all the remaining columns need to be shifted by the same amount for ( size_t i2 = i; i2 < aTabCols.Count(); ++i2 ) aTabCols[i2] += nDiff; aTabCols.SetRight( nTabRight ); } } } const tools::Long nNewRight = aTabCols.GetRight(); SwFrameFormat *pFormat = pTableNd->GetTable().GetFrameFormat(); const sal_Int16 nOriHori = pFormat->GetHoriOrient().GetHoriOrient(); // We can leave the "real" work to the SwTable now SetTabCols( aTabCols, false, static_cast(pBoxFrame) ); // Alignment might have been changed in SetTabCols; restore old value const SwFormatHoriOrient &rHori = pFormat->GetHoriOrient(); SwFormatHoriOrient aHori( rHori ); if ( aHori.GetHoriOrient() != nOriHori ) { aHori.SetHoriOrient( nOriHori ); pFormat->SetFormatAttr( aHori ); } // We switch to left-adjusted for automatic width // We adjust the right border for Border attributes if( !bBalance && nNewRight < nOldRight ) { if( aHori.GetHoriOrient() == text::HoriOrientation::FULL ) { aHori.SetHoriOrient( text::HoriOrientation::LEFT ); pFormat->SetFormatAttr( aHori ); } } getIDocumentState().SetModified(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */