diff options
Diffstat (limited to 'sw/source/core/doc/docsort.cxx')
-rw-r--r-- | sw/source/core/doc/docsort.cxx | 933 |
1 files changed, 933 insertions, 0 deletions
diff --git a/sw/source/core/doc/docsort.cxx b/sw/source/core/doc/docsort.cxx new file mode 100644 index 0000000000..d22ab372e3 --- /dev/null +++ b/sw/source/core/doc/docsort.cxx @@ -0,0 +1,933 @@ +/* -*- 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 <hintids.hxx> +#include <osl/diagnose.h> +#include <unotools/collatorwrapper.hxx> +#include <unotools/localedatawrapper.hxx> +#include <comphelper/processfactory.hxx> +#include <docary.hxx> +#include <fmtanchr.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentState.hxx> +#include <node.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <swundo.hxx> +#include <sortopt.hxx> +#include <docsort.hxx> +#include <UndoSort.hxx> +#include <UndoRedline.hxx> +#include <hints.hxx> +#include <tblsel.hxx> +#include <cellatr.hxx> +#include <redline.hxx> +#include <node2lay.hxx> +#include <frameformats.hxx> +#include <svl/numformat.hxx> + +#include <set> +#include <utility> + +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star; + +SwSortOptions* SwSortElement::pOptions = nullptr; +SwDoc* SwSortElement::pDoc = nullptr; +const FlatFndBox* SwSortElement::pBox = nullptr; +CollatorWrapper* SwSortElement::pSortCollator = nullptr; +lang::Locale* SwSortElement::pLocale = nullptr; +std::optional<OUString> SwSortElement::xLastAlgorithm; +LocaleDataWrapper* SwSortElement::pLclData = nullptr; + +// List of all sorted elements + +/// Construct a SortElement for the Sort +void SwSortElement::Init( SwDoc* pD, const SwSortOptions& rOpt, + FlatFndBox const * pFltBx ) +{ + OSL_ENSURE( !pDoc && !pOptions && !pBox, "Who forgot to call Finit?" ); + pDoc = pD; + pOptions = new SwSortOptions( rOpt ); + pBox = pFltBx; + + LanguageType nLang = rOpt.nLanguage; + if ( nLang.anyOf( + LANGUAGE_NONE, + LANGUAGE_DONTKNOW)) + nLang = GetAppLanguage(); + pLocale = new lang::Locale( LanguageTag::convertToLocale( nLang ) ); + + pSortCollator = new CollatorWrapper( ::comphelper::getProcessComponentContext() ); +} + +void SwSortElement::Finit() +{ + delete pOptions; + pOptions = nullptr; + delete pLocale; + pLocale = nullptr; + xLastAlgorithm.reset(); + delete pSortCollator; + pSortCollator = nullptr; + delete pLclData; + pLclData = nullptr; + pDoc = nullptr; + pBox = nullptr; +} + +SwSortElement::~SwSortElement() +{ +} + +double SwSortElement::StrToDouble( std::u16string_view rStr ) +{ + if( !pLclData ) + pLclData = new LocaleDataWrapper( LanguageTag( *pLocale )); + + rtl_math_ConversionStatus eStatus; + sal_Int32 nEnd; + double nRet = pLclData->stringToDouble( rStr, true, &eStatus, &nEnd ); + + if( rtl_math_ConversionStatus_Ok != eStatus || nEnd == 0 ) + nRet = 0.0; + return nRet; +} + +int SwSortElement::keycompare(const SwSortElement& rCmp, sal_uInt16 nKey) const +{ + int nCmp = 0; + // The actual comparison + const SwSortElement *pOrig, *pCmp; + + const SwSortKey& rSrtKey = pOptions->aKeys[ nKey ]; + if( rSrtKey.eSortOrder == SwSortOrder::Ascending ) + { + pOrig = this; + pCmp = &rCmp; + } + else + { + pOrig = &rCmp; + pCmp = this; + } + + if( rSrtKey.bIsNumeric ) + { + double n1 = pOrig->GetValue( nKey ); + double n2 = pCmp->GetValue( nKey ); + + nCmp = n1 < n2 ? -1 : n1 == n2 ? 0 : 1; + } + else + { + if( !xLastAlgorithm || *xLastAlgorithm != rSrtKey.sSortType ) + { + xLastAlgorithm = rSrtKey.sSortType; + pSortCollator->loadCollatorAlgorithm( *xLastAlgorithm, + *pLocale, + pOptions->bIgnoreCase ? SW_COLLATOR_IGNORES : 0 ); + } + + nCmp = pSortCollator->compareString( + pOrig->GetKey( nKey ), pCmp->GetKey( nKey )); + } + return nCmp; +} + +bool SwSortElement::operator<(const SwSortElement& rCmp) const +{ + // The actual comparison + for(size_t nKey = 0; nKey < pOptions->aKeys.size(); ++nKey) + { + int nCmp = keycompare(rCmp, nKey); + + if (nCmp == 0) + continue; + + return nCmp < 0; + } + + return false; +} + +double SwSortElement::GetValue( sal_uInt16 nKey ) const +{ + return StrToDouble( GetKey( nKey )); +} + +/// SortingElement for Text +SwSortTextElement::SwSortTextElement(const SwNodeIndex& rPos) + : nOrg(rPos.GetIndex()), aPos(rPos) +{ +} + +OUString SwSortTextElement::GetKey(sal_uInt16 nId) const +{ + SwTextNode* pTextNd = aPos.GetNode().GetTextNode(); + if( !pTextNd ) + return OUString(); + + // for TextNodes + const OUString& rStr = pTextNd->GetText(); + + sal_Unicode nDeli = pOptions->cDeli; + sal_uInt16 nDCount = pOptions->aKeys[nId].nColumnId, i = 1; + sal_Int32 nStart = 0; + + // Find the delimiter + while( nStart != -1 && i < nDCount) + { + nStart = rStr.indexOf( nDeli, nStart ); + if( -1 != nStart ) + { + nStart++; + i++; + } + } + + // Found next delimiter or end of String + // and copy + sal_Int32 nEnd = rStr.indexOf( nDeli, nStart+1 ); + if (nEnd == -1) + return rStr.copy( nStart ); + return rStr.copy( nStart, nEnd-nStart ); +} + +/// SortingElement for Tables +SwSortBoxElement::SwSortBoxElement( sal_uInt16 nRC ) + : nRow( nRC ) +{ +} + +/// Get Key for a cell +OUString SwSortBoxElement::GetKey(sal_uInt16 nKey) const +{ + const FndBox_* pFndBox; + sal_uInt16 nCol = pOptions->aKeys[nKey].nColumnId-1; + + if( SwSortDirection::Rows == pOptions->eDirection ) + pFndBox = pBox->GetBox(nCol, nRow); // Sort rows + else + pFndBox = pBox->GetBox(nRow, nCol); // Sort columns + + // Extract the Text + OUStringBuffer aRetStr; + if( pFndBox ) + { // Get StartNode and skip it + const SwTableBox* pMyBox = pFndBox->GetBox(); + OSL_ENSURE(pMyBox, "No atomic Box"); + + if( pMyBox->GetSttNd() ) + { + // Iterate over all the Box's TextNodes + const SwNode *pNd = nullptr, *pEndNd = pMyBox->GetSttNd()->EndOfSectionNode(); + for( SwNodeOffset nIdx = pMyBox->GetSttIdx() + 1; pNd != pEndNd; ++nIdx ) + { + pNd = pDoc->GetNodes()[ nIdx ]; + if( pNd->IsTextNode() ) + aRetStr.append(pNd->GetTextNode()->GetText()); + } + } + } + return aRetStr.makeStringAndClear(); +} + +double SwSortBoxElement::GetValue( sal_uInt16 nKey ) const +{ + const FndBox_* pFndBox; + sal_uInt16 nCol = pOptions->aKeys[nKey].nColumnId-1; + + if( SwSortDirection::Rows == pOptions->eDirection ) + pFndBox = pBox->GetBox(nCol, nRow); // Sort rows + else + pFndBox = pBox->GetBox(nRow, nCol); // Sort columns + + double nVal; + if( pFndBox ) + { + const SwFormat *pFormat = pFndBox->GetBox()->GetFrameFormat(); + if (pDoc->GetNumberFormatter()->IsTextFormat( pFormat->GetTableBoxNumFormat().GetValue())) + nVal = SwSortElement::GetValue( nKey ); + else + nVal = pFormat->GetTableBoxValue().GetValue(); + } + else + nVal = 0; + + return nVal; +} + +/// Sort Text in the Document +bool SwDoc::SortText(const SwPaM& rPaM, const SwSortOptions& rOpt) +{ + // Check if Frame is in the Text + auto [pStart, pEnd] = rPaM.StartEnd(); // SwPosition* + + // Set index to the Selection's start + for(sw::SpzFrameFormat* pFormat: *GetSpzFrameFormats()) + { + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwNode const*const pAnchorNode = pAnchor->GetAnchorNode(); + + if (pAnchorNode && (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) && + pStart->GetNode() <= *pAnchorNode && *pAnchorNode <= pEnd->GetNode() ) + return false; + } + + // Check if only TextNodes are within the Selection + { + SwNodeOffset nStart = pStart->GetNodeIndex(), + nEnd = pEnd->GetNodeIndex(); + while( nStart <= nEnd ) + // Iterate over a selected range + if( !GetNodes()[ nStart++ ]->IsTextNode() ) + return false; + } + + bool const bUndo = GetIDocumentUndoRedo().DoesUndo(); + if( bUndo ) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + } + + SwPaM* pRedlPam = nullptr; + SwUndoRedlineSort* pRedlUndo = nullptr; + SwUndoSort* pUndoSort = nullptr; + + // To-Do - add 'SwExtraRedlineTable' also ? + if( getIDocumentRedlineAccess().IsRedlineOn() || (!getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + pRedlPam = new SwPaM( pStart->GetNode(), pEnd->GetNode(), SwNodeOffset(-1), SwNodeOffset(1) ); + SwContentNode* pCNd = pRedlPam->GetMarkContentNode(); + if( pCNd ) + pRedlPam->GetMark()->SetContent( pCNd->Len() ); + + if( getIDocumentRedlineAccess().IsRedlineOn() && !IDocumentRedlineAccess::IsShowOriginal( getIDocumentRedlineAccess().GetRedlineFlags() ) ) + { + if( bUndo ) + { + pRedlUndo = new SwUndoRedlineSort( *pRedlPam,rOpt ); + GetIDocumentUndoRedo().DoUndo(false); + } + // First copy the range + SwNodeIndex aEndIdx( pEnd->GetNode(), 1 ); + SwNodeRange aRg( pStart->GetNode(), aEndIdx.GetNode() ); + GetNodes().Copy_( aRg, aEndIdx.GetNode() ); + + // range is new from pEnd->nNode+1 to aEndIdx + getIDocumentRedlineAccess().DeleteRedline( *pRedlPam, true, RedlineType::Any ); + + pRedlPam->GetMark()->Assign( pEnd->GetNode(), SwNodeOffset(1) ); + + pRedlPam->GetPoint()->Assign( aEndIdx.GetNode() ); + pCNd = pRedlPam->GetPointContentNode(); + sal_Int32 nCLen = 0; + if( !pCNd ) + { + pCNd = GetNodes()[ aEndIdx.GetIndex()-SwNodeOffset(1) ]->GetContentNode(); + if( pCNd ) + { + nCLen = pCNd->Len(); + pRedlPam->GetPoint()->Assign( *pCNd ); + } + } + if (pCNd) + pRedlPam->GetPoint()->SetContent( nCLen ); + + if( pRedlUndo ) + pRedlUndo->SetValues( rPaM ); + } + else + { + getIDocumentRedlineAccess().DeleteRedline( *pRedlPam, true, RedlineType::Any ); + delete pRedlPam; + pRedlPam = nullptr; + } + } + + SwNodeIndex aStart(pStart->GetNode()); + SwSortElement::Init( this, rOpt ); + std::multiset<SwSortTextElement> aSortSet; + while( aStart <= pEnd->GetNode() ) + { + // Iterate over a selected range + aSortSet.insert(SwSortTextElement(aStart)); + ++aStart; + } + + // Now comes the tricky part: Move Nodes (and always keep Undo in mind) + SwNodeOffset nBeg = pStart->GetNodeIndex(); + SwNodeRange aRg( aStart, aStart ); + + if( bUndo && !pRedlUndo ) + { + pUndoSort = new SwUndoSort(rPaM, rOpt); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndoSort)); + } + + GetIDocumentUndoRedo().DoUndo(false); + + SwNodeOffset n(0); + for (const auto& rElem : aSortSet) + { + aStart = nBeg + n; + aRg.aStart = rElem.aPos.GetIndex(); + aRg.aEnd = aRg.aStart.GetIndex() + 1; + + // Move Nodes + getIDocumentContentOperations().MoveNodeRange( aRg, aStart.GetNode(), + SwMoveFlags::DEFAULT ); + + // Insert Move in Undo + if(pUndoSort) + { + pUndoSort->Insert(rElem.nOrg, nBeg + n); + } + ++n; + } + // Delete all elements from the SortArray + aSortSet.clear(); + SwSortElement::Finit(); + + if( pRedlPam ) + { + if( pRedlUndo ) + { + pRedlUndo->SetSaveRange( *pRedlPam ); + // UGLY: temp. enable Undo + GetIDocumentUndoRedo().DoUndo(true); + GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pRedlUndo) ); + GetIDocumentUndoRedo().DoUndo(false); + } + + // nBeg is start of sorted range + SwNodeIndex aSttIdx( GetNodes(), nBeg ); + + // the copied range is deleted + SwRangeRedline *const pDeleteRedline( + new SwRangeRedline( RedlineType::Delete, *pRedlPam )); + + // pRedlPam points to nodes that may be deleted (hidden) by + // AppendRedline, so adjust it beforehand to prevent ASSERT + pRedlPam->GetPoint()->Assign(aSttIdx); + + getIDocumentRedlineAccess().AppendRedline(pDeleteRedline, true); + + // the sorted range is inserted + getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, *pRedlPam ), true); + + if( pRedlUndo ) + { + SwNodeIndex aInsEndIdx( pRedlPam->GetMark()->GetNode(), -1 ); + SwContentNode *const pContentNode = aInsEndIdx.GetNode().GetContentNode(); + pRedlPam->GetMark()->Assign( *pContentNode, pContentNode->Len() ); + + pRedlUndo->SetValues( *pRedlPam ); + } + + delete pRedlPam; + pRedlPam = nullptr; + } + GetIDocumentUndoRedo().DoUndo( bUndo ); + if( bUndo ) + { + GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + + return true; +} + +/// Sort Table in the Document +bool SwDoc::SortTable(const SwSelBoxes& rBoxes, const SwSortOptions& rOpt) +{ + // Via SwDoc for Undo! + OSL_ENSURE( !rBoxes.empty(), "no valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + // We begin sorting + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( rBoxes, &aFndBox ); + ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara ); + } + + if(aFndBox.GetLines().empty()) + return false; + + if( !getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() ) + getIDocumentRedlineAccess().DeleteRedline( *pTableNd, true, RedlineType::Any ); + + FndLines_t::size_type nStart = 0; + if( pTableNd->GetTable().GetRowsToRepeat() > 0 && rOpt.eDirection == SwSortDirection::Rows ) + { + // Uppermost selected Cell + FndLines_t& rLines = aFndBox.GetLines(); + + while( nStart < rLines.size() ) + { + // Respect Split Merge nesting, + // extract the upper most + SwTableLine* pLine = rLines[nStart]->GetLine(); + while ( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + if( pTableNd->GetTable().IsHeadline( *pLine ) ) + nStart++; + else + break; + } + // Are all selected in the HeaderLine? -> no Offset + if( nStart == rLines.size() ) + nStart = 0; + } + + pTableNd->GetTable().SwitchFormulasToRelativeRepresentation(); + + // Table as a flat array structure + FlatFndBox aFlatBox(this, aFndBox); + + if(!aFlatBox.IsSymmetric()) + return false; + + // Delete HTML layout + pTableNd->GetTable().SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); + + // #i37739# A simple 'MakeFrames' after the node sorting + // does not work if the table is inside a frame and has no prev/next. + SwNode2LayoutSaveUpperFrames aNode2Layout(*pTableNd); + + // Delete the Table's Frames + pTableNd->DelFrames(); + // ? TL_CHART2: ? + + SwUndoSort* pUndoSort = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndoSort = new SwUndoSort( rBoxes[0]->GetSttIdx(), + rBoxes.back()->GetSttIdx(), + *pTableNd, rOpt, aFlatBox.HasItemSets() ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndoSort)); + } + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + // Insert KeyElements + sal_uInt16 nCount = (rOpt.eDirection == SwSortDirection::Rows) ? + aFlatBox.GetRows() : aFlatBox.GetCols(); + + // Sort SortList by Key + SwSortElement::Init( this, rOpt, &aFlatBox ); + std::multiset<SwSortBoxElement> aSortList; + + // When sorting, do not include the first row if the HeaderLine is repeated + for( sal_uInt16 i = o3tl::narrowing<sal_uInt16>(nStart); i < nCount; ++i) + { + aSortList.insert(SwSortBoxElement(i)); + } + + // Move after Sorting + SwMovedBoxes aMovedList; + sal_uInt16 i = 0; + for (const auto& rElem : aSortList) + { + if(rOpt.eDirection == SwSortDirection::Rows) + { + MoveRow(this, aFlatBox, rElem.nRow, i+nStart, aMovedList, pUndoSort); + } + else + { + MoveCol(this, aFlatBox, rElem.nRow, i+nStart, aMovedList, pUndoSort); + } + ++i; + } + + // Restore table frames: + // #i37739# A simple 'MakeFrames' after the node sorting + // does not work if the table is inside a frame and has no prev/next. + const SwNodeOffset nIdx = pTableNd->GetIndex(); + aNode2Layout.RestoreUpperFrames( GetNodes(), nIdx, nIdx + 1 ); + + // TL_CHART2: need to inform chart of probably changed cell names + UpdateCharts( pTableNd->GetTable().GetFrameFormat()->GetName() ); + + // Delete all Elements in the SortArray + aSortList.clear(); + SwSortElement::Finit(); + + getIDocumentState().SetModified(); + return true; +} + +/// Move a row +void MoveRow(SwDoc* pDoc, const FlatFndBox& rBox, sal_uInt16 nS, sal_uInt16 nT, + SwMovedBoxes& rMovedList, SwUndoSort* pUD) +{ + for( sal_uInt16 i=0; i < rBox.GetCols(); ++i ) + { // Get old cell position and remember it + const FndBox_* pSource = rBox.GetBox(i, nS); + + // new cell position + const FndBox_* pTarget = rBox.GetBox(i, nT); + + const SwTableBox* pT = pTarget->GetBox(); + const SwTableBox* pS = pSource->GetBox(); + + bool bMoved = rMovedList.GetPos(pT) != USHRT_MAX; + + // and move it + MoveCell(pDoc, pS, pT, bMoved, pUD); + + rMovedList.push_back(pS); + + if( pS != pT ) + { + SwFrameFormat* pTFormat = pT->GetFrameFormat(); + const SfxItemSet* pSSet = rBox.GetItemSet( i, nS ); + + if( pSSet || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMAT ) || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMULA ) || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_VALUE ) ) + { + pTFormat = const_cast<SwTableBox*>(pT)->ClaimFrameFormat(); + pTFormat->LockModify(); + if( pTFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ) ) + pTFormat->ResetFormatAttr( RES_VERT_ORIENT ); + + if( pSSet ) + pTFormat->SetFormatAttr( *pSSet ); + pTFormat->UnlockModify(); + } + } + } +} + +/// Move a column +void MoveCol(SwDoc* pDoc, const FlatFndBox& rBox, sal_uInt16 nS, sal_uInt16 nT, + SwMovedBoxes& rMovedList, SwUndoSort* pUD) +{ + for(sal_uInt16 i=0; i < rBox.GetRows(); ++i) + { // Get old cell position and remember it + const FndBox_* pSource = rBox.GetBox(nS, i); + + // new cell position + const FndBox_* pTarget = rBox.GetBox(nT, i); + + // and move it + const SwTableBox* pT = pTarget->GetBox(); + const SwTableBox* pS = pSource->GetBox(); + + // and move it + bool bMoved = rMovedList.GetPos(pT) != USHRT_MAX; + MoveCell(pDoc, pS, pT, bMoved, pUD); + + rMovedList.push_back(pS); + + if( pS != pT ) + { + SwFrameFormat* pTFormat = pT->GetFrameFormat(); + const SfxItemSet* pSSet = rBox.GetItemSet( nS, i ); + + if( pSSet || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMAT ) || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMULA ) || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_VALUE ) ) + { + pTFormat = const_cast<SwTableBox*>(pT)->ClaimFrameFormat(); + pTFormat->LockModify(); + if( pTFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ) ) + pTFormat->ResetFormatAttr( RES_VERT_ORIENT ); + + if( pSSet ) + pTFormat->SetFormatAttr( *pSSet ); + pTFormat->UnlockModify(); + } + } + } +} + +/// Move a single Cell +void MoveCell(SwDoc* pDoc, const SwTableBox* pSource, const SwTableBox* pTar, + bool bMovedBefore, SwUndoSort* pUD) +{ + OSL_ENSURE(pSource && pTar,"Source or target missing"); + + if(pSource == pTar) + return; + + if(pUD) + pUD->Insert( pSource->GetName(), pTar->GetName() ); + + // Set Pam source to the first ContentNode + SwNodeRange aRg( *pSource->GetSttNd(), SwNodeOffset(0), *pSource->GetSttNd() ); + SwNode* pNd = pDoc->GetNodes().GoNext( &aRg.aStart ); + + // If the Cell (Source) wasn't moved + // -> insert an empty Node and move the rest or the Mark + // points to the first ContentNode + if( pNd->StartOfSectionNode() == pSource->GetSttNd() ) + pNd = pDoc->GetNodes().MakeTextNode( aRg.aStart.GetNode(), + pDoc->GetDfltTextFormatColl() ); + aRg.aEnd = *pNd->EndOfSectionNode(); + + // If the Target is empty (there is one empty Node) + // -> move and delete it + SwNodeIndex aTar( *pTar->GetSttNd() ); + pNd = pDoc->GetNodes().GoNext( &aTar ); // next ContentNode + SwNodeOffset nCount = pNd->EndOfSectionIndex() - pNd->StartOfSectionIndex(); + + bool bDelFirst = false; + if( nCount == SwNodeOffset(2) ) + { + OSL_ENSURE( pNd->GetContentNode(), "No ContentNode"); + bDelFirst = !pNd->GetContentNode()->Len() && bMovedBefore; + } + + if(!bDelFirst) + { // We already have Content -> old Content Section Down + SwNodeRange aRgTar( aTar.GetNode(), SwNodeOffset(0), *pNd->EndOfSectionNode() ); + pDoc->GetNodes().SectionDown( &aRgTar ); + } + + // Insert the Source + SwNodeIndex aIns( *pTar->GetSttNd()->EndOfSectionNode() ); + pDoc->getIDocumentContentOperations().MoveNodeRange( aRg, aIns.GetNode(), + SwMoveFlags::DEFAULT ); + + // If first Node is empty -> delete it + if(bDelFirst) + pDoc->GetNodes().Delete( aTar ); +} + +/// Generate two-dimensional array of FndBoxes +FlatFndBox::FlatFndBox(SwDoc* pDocPtr, const FndBox_& rBoxRef) : + m_pDoc(pDocPtr), + m_nRow(0), + m_nCol(0) +{ // If the array is symmetric + m_bSym = CheckLineSymmetry(rBoxRef); + if( !m_bSym ) + return; + + // Determine column/row count + m_nCols = GetColCount(rBoxRef); + m_nRows = GetRowCount(rBoxRef); + + // Create linear array + size_t nCount = static_cast<size_t>(m_nRows) * m_nCols; + m_pArr = std::make_unique<FndBox_ const *[]>(nCount); + memset(m_pArr.get(), 0, sizeof(const FndBox_*) * nCount); + + FillFlat( rBoxRef ); +} + +FlatFndBox::~FlatFndBox() +{ +} + +/// All Lines of a Box need to have same number of Boxes +bool FlatFndBox::CheckLineSymmetry(const FndBox_& rBox) +{ + const FndLines_t &rLines = rBox.GetLines(); + FndBoxes_t::size_type nBoxes {0}; + + for (FndLines_t::size_type i=0; i < rLines.size(); ++i) + { + const FndLine_* pLn = rLines[i].get(); + const FndBoxes_t& rBoxes = pLn->GetBoxes(); + + // Number of Boxes of all Lines is unequal -> no symmetry + if( i && nBoxes != rBoxes.size()) + return false; + + nBoxes = rBoxes.size(); + if( !CheckBoxSymmetry( *pLn ) ) + return false; + } + return true; +} + +/// Check Box for symmetry (All Boxes of a Line need to have same number of Lines) +bool FlatFndBox::CheckBoxSymmetry(const FndLine_& rLn) +{ + const FndBoxes_t &rBoxes = rLn.GetBoxes(); + FndLines_t::size_type nLines {0}; + + for (FndBoxes_t::size_type i = 0; i < rBoxes.size(); ++i) + { + FndBox_ const*const pBox = rBoxes[i].get(); + const FndLines_t& rLines = pBox->GetLines(); + + // Number of Lines of all Boxes is unequal -> no symmetry + if( i && nLines != rLines.size() ) + return false; + + nLines = rLines.size(); + if( nLines && !CheckLineSymmetry( *pBox ) ) + return false; + } + return true; +} + +/// Maximum count of Columns (Boxes) +sal_uInt16 FlatFndBox::GetColCount(const FndBox_& rBox) +{ + const FndLines_t& rLines = rBox.GetLines(); + // Iterate over Lines + if( rLines.empty() ) + return 1; + + sal_uInt16 nSum = 0; + for (const auto & pLine : rLines) + { + // The Boxes of a Line + sal_uInt16 nCount = 0; + const FndBoxes_t& rBoxes = pLine->GetBoxes(); + for (const auto &rpB : rBoxes) + { // Iterate recursively over the Lines + nCount += rpB->GetLines().empty() ? 1 : GetColCount(*rpB); + } + + if( nSum < nCount ) + nSum = nCount; + } + return nSum; +} + +/// Maximum count of Rows (Lines) +sal_uInt16 FlatFndBox::GetRowCount(const FndBox_& rBox) +{ + const FndLines_t& rLines = rBox.GetLines(); + if( rLines.empty() ) + return 1; + + sal_uInt16 nLines = 0; + for (const auto & pLine : rLines) + { // The Boxes of a Line + const FndBoxes_t& rBoxes = pLine->GetBoxes(); + sal_uInt16 nLn = 1; + for (const auto &rpB : rBoxes) + { + if (!rpB->GetLines().empty()) + { // Iterate recursively over the Lines + nLn = std::max(GetRowCount(*rpB), nLn); + } + } + + nLines = nLines + nLn; + } + return nLines; +} + +/// Create a linear array of atomic FndBoxes +void FlatFndBox::FillFlat(const FndBox_& rBox, bool bLastBox) +{ + bool bModRow = false; + const FndLines_t& rLines = rBox.GetLines(); + + // Iterate over Lines + sal_uInt16 nOldRow = m_nRow; + for (const auto & pLine : rLines) + { + // The Boxes of a Line + const FndBoxes_t& rBoxes = pLine->GetBoxes(); + sal_uInt16 nOldCol = m_nCol; + for( FndBoxes_t::size_type j = 0; j < rBoxes.size(); ++j ) + { + // Check the Box if it's an atomic one + const FndBox_ *const pBox = rBoxes[j].get(); + + if( pBox->GetLines().empty() ) + { + // save it + sal_uInt16 nOff = m_nRow * m_nCols + m_nCol; + m_pArr[nOff] = pBox; + + // Save the Formula/Format/Value values + const SwFrameFormat* pFormat = pBox->GetBox()->GetFrameFormat(); + if( SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_FORMAT ) || + SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_FORMULA ) || + SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_VALUE ) ) + { + SfxItemSetFixed< + RES_VERT_ORIENT, RES_VERT_ORIENT, + RES_BOXATR_FORMAT, RES_BOXATR_VALUE> + aSet(m_pDoc->GetAttrPool()); + aSet.Put( pFormat->GetAttrSet() ); + if( m_vItemSets.empty() ) + { + size_t nCount = static_cast<size_t>(m_nRows) * m_nCols; + m_vItemSets.resize(nCount); + } + m_vItemSets[nOff].emplace(std::move(aSet)); + } + + bModRow = true; + } + else + { + // Iterate recursively over the Lines of a Box + FillFlat( *pBox, ( j+1 == rBoxes.size() ) ); + } + m_nCol++; + } + if(bModRow) + m_nRow++; + m_nCol = nOldCol; + } + if(!bLastBox) + m_nRow = nOldRow; +} + +/// Access a specific Cell +const FndBox_* FlatFndBox::GetBox(sal_uInt16 n_Col, sal_uInt16 n_Row) const +{ + sal_uInt16 nOff = n_Row * m_nCols + n_Col; + const FndBox_* pTmp = m_pArr[nOff]; + + OSL_ENSURE(n_Col < m_nCols && n_Row < m_nRows && pTmp, "invalid array access"); + return pTmp; +} + +const SfxItemSet* FlatFndBox::GetItemSet(sal_uInt16 n_Col, sal_uInt16 n_Row) const +{ + OSL_ENSURE( m_vItemSets.empty() || ( n_Col < m_nCols && n_Row < m_nRows), "invalid array access"); + + if (m_vItemSets.empty()) { + return nullptr; + } + auto const & el = m_vItemSets[unsigned(n_Row * m_nCols) + n_Col]; + return el ? &*el : nullptr; +} + +sal_uInt16 SwMovedBoxes::GetPos(const SwTableBox* pTableBox) const +{ + std::vector<const SwTableBox*>::const_iterator it = std::find(mBoxes.begin(), mBoxes.end(), pTableBox); + return it == mBoxes.end() ? USHRT_MAX : it - mBoxes.begin(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |